From 68b642a7f80df960bcd43aabf62aa1dafc3e3891 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Thu, 17 Apr 2025 18:44:13 -0400 Subject: [PATCH 01/45] Fix race condition in IteratorSafeOrderedReferenceSet (#278) * Fix race condition in IteratorSafeOrderedReferenceSet * Use cached firstInvalidIndex at the beginning of the method --- ...004-Pufferfish-Optimize-mob-spawning.patch | 96 +++++++++++++++++-- 1 file changed, 90 insertions(+), 6 deletions(-) diff --git a/leaf-server/paper-patches/features/0004-Pufferfish-Optimize-mob-spawning.patch b/leaf-server/paper-patches/features/0004-Pufferfish-Optimize-mob-spawning.patch index 345bc1c6..e9697ac3 100644 --- a/leaf-server/paper-patches/features/0004-Pufferfish-Optimize-mob-spawning.patch +++ b/leaf-server/paper-patches/features/0004-Pufferfish-Optimize-mob-spawning.patch @@ -23,7 +23,7 @@ and, in my opinion, worth the low risk of minor mob-spawning-related inconsistencies. diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java -index c21e00812f1aaa1279834a0562d360d6b89e146c..4ae478c04ef44c91408a7f3f0405291f91794873 100644 +index c21e00812f1aaa1279834a0562d360d6b89e146c..19e0bf50cf8863f42ce19c3d0d8911c0c9b8ad4a 100644 --- a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java +++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java @@ -10,7 +10,7 @@ public final class IteratorSafeOrderedReferenceSet { @@ -31,7 +31,7 @@ index c21e00812f1aaa1279834a0562d360d6b89e146c..4ae478c04ef44c91408a7f3f0405291f private final Reference2IntLinkedOpenHashMap indexMap; - private int firstInvalidIndex = -1; -+ private volatile int firstInvalidIndex = -1; // Leaf - Async mob spawning - volatile ++ private final java.util.concurrent.atomic.AtomicInteger firstInvalidIndex = new java.util.concurrent.atomic.AtomicInteger(-1); // Leaf - Pufferfish - Async mob spawning - atomic /* list impl */ private E[] listElements; @@ -44,7 +44,22 @@ index c21e00812f1aaa1279834a0562d360d6b89e146c..4ae478c04ef44c91408a7f3f0405291f public IteratorSafeOrderedReferenceSet() { this(16, 0.75f, 16, 0.2); -@@ -79,7 +79,7 @@ public final class IteratorSafeOrderedReferenceSet { +@@ -74,16 +74,26 @@ public final class IteratorSafeOrderedReferenceSet { + } + */ + ++ // Pufferfish start - async mob spawning ++ // TODO: backup plan if the issue persists, don't run ++ // this.set.finishRawIterator() or defrag() off-main ++ /* ++ protected final boolean allowSafeIteration() { ++ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThread(); ++ } ++ */ ++ // Pufferfish end - async mob spawning ++ + private double getFragFactor() { + return 1.0 - ((double)this.indexMap.size() / (double)this.listSize); } public int createRawIterator() { @@ -53,7 +68,12 @@ index c21e00812f1aaa1279834a0562d360d6b89e146c..4ae478c04ef44c91408a7f3f0405291f if (this.indexMap.isEmpty()) { return -1; } else { -@@ -100,7 +100,7 @@ public final class IteratorSafeOrderedReferenceSet { +- return this.firstInvalidIndex == 0 ? this.indexMap.getInt(this.indexMap.firstKey()) : 0; ++ return this.firstInvalidIndex.get() == 0 ? this.indexMap.getInt(this.indexMap.firstKey()) : 0; // Leaf - Pufferfish - Async mob spawning + } + } + +@@ -100,7 +110,7 @@ public final class IteratorSafeOrderedReferenceSet { } public void finishRawIterator() { @@ -62,7 +82,19 @@ index c21e00812f1aaa1279834a0562d360d6b89e146c..4ae478c04ef44c91408a7f3f0405291f if (this.getFragFactor() >= this.maxFragFactor) { this.defrag(); } -@@ -117,7 +117,7 @@ public final class IteratorSafeOrderedReferenceSet { +@@ -110,14 +120,17 @@ public final class IteratorSafeOrderedReferenceSet { + public boolean remove(final E element) { + final int index = this.indexMap.removeInt(element); + if (index >= 0) { +- if (this.firstInvalidIndex < 0 || index < this.firstInvalidIndex) { +- this.firstInvalidIndex = index; ++ // Leaf start - Pufferfish - Async mob spawning ++ int firstInvalidIndex = this.firstInvalidIndex.get(); ++ if (firstInvalidIndex < 0 || index < firstInvalidIndex) { ++ this.firstInvalidIndex.set(index); + } ++ // Leaf end - Pufferfish - Async mob spawning + if (this.listElements[index] != element) { throw new IllegalStateException(); } this.listElements[index] = null; @@ -71,7 +103,50 @@ index c21e00812f1aaa1279834a0562d360d6b89e146c..4ae478c04ef44c91408a7f3f0405291f this.defrag(); } //this.check(); -@@ -219,7 +219,7 @@ public final class IteratorSafeOrderedReferenceSet { +@@ -149,14 +162,17 @@ public final class IteratorSafeOrderedReferenceSet { + } + + private void defrag() { +- if (this.firstInvalidIndex < 0) { ++ // Leaf start - Pufferfish - Async mob spawning ++ int firstInvalidIndex = this.firstInvalidIndex.get(); ++ if (firstInvalidIndex < 0) { + return; // nothing to do + } ++ // Leaf end - Pufferfish - Async mob spawning + + if (this.indexMap.isEmpty()) { + Arrays.fill(this.listElements, 0, this.listSize, null); + this.listSize = 0; +- this.firstInvalidIndex = -1; ++ this.firstInvalidIndex.set(-1); // Leaf - Pufferfish - Async mob spawning + //this.check(); + return; + } +@@ -166,11 +182,11 @@ public final class IteratorSafeOrderedReferenceSet { + int lastValidIndex; + java.util.Iterator> iterator; + +- if (this.firstInvalidIndex == 0) { ++ if (firstInvalidIndex == 0) { // Leaf - Pufferfish - Async mob spawning + iterator = this.indexMap.reference2IntEntrySet().fastIterator(); + lastValidIndex = 0; + } else { +- lastValidIndex = this.firstInvalidIndex; ++ lastValidIndex = firstInvalidIndex; // Leaf - Pufferfish - Async mob spawning + final E key = backingArray[lastValidIndex - 1]; + iterator = this.indexMap.reference2IntEntrySet().fastIterator(new Reference2IntMap.Entry() { + @Override +@@ -201,7 +217,7 @@ public final class IteratorSafeOrderedReferenceSet { + // cleanup end + Arrays.fill(backingArray, lastValidIndex, this.listSize, null); + this.listSize = lastValidIndex; +- this.firstInvalidIndex = -1; ++ this.firstInvalidIndex.set(-1); // Leaf - Pufferfish - Async mob spawning + //this.check(); + } + +@@ -219,7 +235,7 @@ public final class IteratorSafeOrderedReferenceSet { } public IteratorSafeOrderedReferenceSet.Iterator iterator(final int flags) { @@ -80,3 +155,12 @@ index c21e00812f1aaa1279834a0562d360d6b89e146c..4ae478c04ef44c91408a7f3f0405291f return new BaseIterator<>(this, true, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize); } +@@ -306,7 +322,7 @@ public final class IteratorSafeOrderedReferenceSet { + } + this.lastReturned = null; + this.finished = true; +- this.set.finishRawIterator(); ++ this.set.finishRawIterator(); // Pufferfish - async mob spawning - diff on change + } + } + } From d0944505c2cba40ad4e4ef437574570099723c0d Mon Sep 17 00:00:00 2001 From: Creeam <102713261+HaHaWTH@users.noreply.github.com> Date: Sat, 19 Apr 2025 22:19:26 +0800 Subject: [PATCH 02/45] Adjust throttle hopper default value Vanilla throttles this for 8 ticks, leave it here as the default value --- .../dreeam/leaf/config/modules/opt/ThrottleHopperWhenFull.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleHopperWhenFull.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleHopperWhenFull.java index 62083380..236ea3a9 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleHopperWhenFull.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/ThrottleHopperWhenFull.java @@ -10,7 +10,7 @@ public class ThrottleHopperWhenFull extends ConfigModules { } public static boolean enabled = false; - public static int skipTicks = 0; + public static int skipTicks = 8; @Override public void onLoaded() { From e8915799db960ac81dfc1e6a6fb9bb3a650d28e7 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Sun, 20 Apr 2025 01:25:00 -0400 Subject: [PATCH 03/45] [ci skip] Move TODOs to dev/1.21.5 --- todos.md | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 todos.md diff --git a/todos.md b/todos.md deleted file mode 100644 index 22e3b6eb..00000000 --- a/todos.md +++ /dev/null @@ -1,13 +0,0 @@ -# Gale TODOs -- [ ] Do a benchmark for `Remove streams and iterators from range check`, getEffectiveRange in ChunkMap -- [ ] Do benchmark for reduce entity fluid lookup on 1.21.3 -- [ ] Check gale force load chunk option whether need or whether need to change. -- [ ] check reduce array allocation, whether need do clone for some reference, to prevent potential issues.. or add debug command for debugging?. - -# Leaf TODOs -- [ ] refactor leaves protocol manager opt and pr it. -- [ ] Transfer patch notes to file for Gale and Leaf -- [ ] Add spigot config unknown message to leaf docs -- [ ] Add server full join config explanation to docs -- [ ] Add purpur config changes to docs moved config -- [ ] check Dont send useless entity packets From cda5af1083db4ea4c500d7b219c19f78f2121d1b Mon Sep 17 00:00:00 2001 From: hayanesuru <68378576+hayanesuru@users.noreply.github.com> Date: Sun, 20 Apr 2025 20:29:26 +0800 Subject: [PATCH 04/45] update Async target finding (#291) * update Async target finding * create TargetingConditions instead of reusing it * cleanup * only add synchronized to Sensing when enable AsyncTargetFinding * cleanup * use Release order to store isSearching * fix compile * atomic removal check * fix Entity#isRemoved doesn't init * opt isRemoved use acquire memory order to load --- .../features/0156-Async-Target-Finding.patch | 319 ------------- .../features/0156-Async-target-finding.patch | 437 ++++++++++++++++++ 2 files changed, 437 insertions(+), 319 deletions(-) delete mode 100644 leaf-server/minecraft-patches/features/0156-Async-Target-Finding.patch create mode 100644 leaf-server/minecraft-patches/features/0156-Async-target-finding.patch diff --git a/leaf-server/minecraft-patches/features/0156-Async-Target-Finding.patch b/leaf-server/minecraft-patches/features/0156-Async-Target-Finding.patch deleted file mode 100644 index 9aaa7f88..00000000 --- a/leaf-server/minecraft-patches/features/0156-Async-Target-Finding.patch +++ /dev/null @@ -1,319 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Sat, 29 Mar 2025 13:40:46 +0100 -Subject: [PATCH] Async Target Finding - - -diff --git a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -index 41ee3cdc45ecc8376a2203ed588bb544ed377294..ed731177585051abbf129a48dfe4766265cf5617 100644 ---- a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -+++ b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -@@ -16,9 +16,37 @@ public class NearestAttackableTargetGoal extends TargetG - protected final Class targetType; - protected final int randomInterval; - @Nullable -- protected LivingEntity target; -+ protected volatile LivingEntity target; // Leaf - Async Target Finding - protected TargetingConditions targetConditions; - -+ // Leaf start - Async Target Finding -+ // Single thread executor to prevent overwhelming the server -+ private static final java.util.concurrent.ExecutorService TARGET_FINDER_EXECUTOR = java.util.concurrent.Executors.newSingleThreadExecutor(r -> { -+ Thread thread = new Thread(r, "Leaf - Target-Finder-Thread"); -+ thread.setDaemon(true); -+ thread.setPriority(Thread.MIN_PRIORITY); // Lower priority to avoid competing with main thread -+ return thread; -+ }); -+ -+ // Flag to track if a search is in progress -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); -+ static { -+ Runtime.getRuntime().addShutdownHook(new Thread(() -> { -+ try { -+ TARGET_FINDER_EXECUTOR.shutdown(); -+ TARGET_FINDER_EXECUTOR.awaitTermination(2, java.util.concurrent.TimeUnit.SECONDS); -+ } catch (InterruptedException e) { -+ Thread.currentThread().interrupt(); -+ } finally { -+ if (!TARGET_FINDER_EXECUTOR.isTerminated()) { -+ TARGET_FINDER_EXECUTOR.shutdownNow(); -+ } -+ } -+ })); -+ } -+ // Leaf end - Async Target Finding -+ - public NearestAttackableTargetGoal(Mob mob, Class targetType, boolean mustSee) { - this(mob, targetType, 10, mustSee, false, null); - } -@@ -46,8 +74,14 @@ public class NearestAttackableTargetGoal extends TargetG - if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) { - return false; - } else { -- this.findTarget(); -- return this.target != null; -+ // Leaf start - Async Target Finding -+ findTarget(); -+ LivingEntity pending = pendingTarget.getAndSet(null); -+ if (pending != null && !pending.isRemoved() && pending.isAlive()) { -+ this.target = pending; -+ } -+ return this.target != null && this.target.isAlive() && !this.target.isRemoved(); -+ // Leaf end - Async Target Finding - } - } - -@@ -55,25 +89,239 @@ public class NearestAttackableTargetGoal extends TargetG - return this.mob.getBoundingBox().inflate(targetDistance, targetDistance, targetDistance); - } - -+ // Leaf start - Async Target Finding -+ // Async find target implementation with safer entity handling - protected void findTarget() { -- ServerLevel serverLevel = getServerLevel(this.mob); -- if (this.targetType != Player.class && this.targetType != ServerPlayer.class) { -- this.target = serverLevel.getNearestEntity( -- this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true), -- this.getTargetConditions(), -- this.mob, -- this.mob.getX(), -- this.mob.getEyeY(), -- this.mob.getZ() -- ); -- } else { -- this.target = serverLevel.getNearestPlayer(this.getTargetConditions(), this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); -+ // If async is disabled or we're already searching, use sync method -+ if (!org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled || !isSearching.compareAndSet(false, true)) { -+ findTargetSync(); -+ return; - } -+ -+ // Capture mutable state to avoid race conditions -+ final Mob mob = this.mob; -+ -+ // Safety check -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.set(false); -+ return; -+ } -+ -+ final double x = mob.getX(); -+ final double y = mob.getEyeY(); -+ final double z = mob.getZ(); -+ final double followDistance = this.getFollowDistance(); -+ final TargetingConditions targetConditions = this.getTargetConditions(); -+ final Class targetType = this.targetType; -+ -+ // Start async search with immutable captured state - using submit instead of runAsync -+ java.util.concurrent.CompletableFuture.supplyAsync(() -> { -+ try { -+ ServerLevel serverLevel = getServerLevel(mob); -+ if (serverLevel == null) { -+ return null; -+ } -+ if (mob.isRemoved() || !mob.isAlive()) { -+ return null; -+ } -+ -+ try { -+ if (targetType != Player.class && targetType != ServerPlayer.class) { -+ AABB searchArea = new AABB( -+ x - followDistance, y - followDistance, z - followDistance, -+ x + followDistance, y + followDistance, z + followDistance -+ ); -+ -+ java.util.List entities = null; -+ try { -+ entities = mob.level().getEntitiesOfClass(targetType, searchArea, entity -> true); -+ } catch (Exception e) { -+ System.err.println("Error getting entities: " + e.getMessage()); -+ return null; -+ } -+ -+ if (entities != null && !entities.isEmpty()) { -+ return findNearestEntitySafely(entities, targetConditions, mob, x, y, z, serverLevel); -+ } -+ } else { -+ return findNearestPlayerSafely(targetConditions, mob, x, y, z, serverLevel); -+ } -+ } catch (Exception e) { -+ System.err.println("Error finding entities in async target finder: " + e.getMessage()); -+ } -+ -+ return null; -+ } catch (Exception e) { -+ System.err.println("Error during async target finding: " + e.getMessage()); -+ return null; -+ } finally { -+ isSearching.set(false); -+ } -+ }, TARGET_FINDER_EXECUTOR).thenAccept(result -> { -+ if (result != null && result.isAlive() && !result.isRemoved()) { -+ pendingTarget.set(result); -+ } -+ }); - } - -+ @Nullable -+ private LivingEntity findNearestEntitySafely( -+ java.util.List entities, -+ TargetingConditions conditions, -+ Mob source, -+ double x, -+ double y, -+ double z, -+ ServerLevel level) { -+ -+ if (entities == null || entities.isEmpty() || level == null) { -+ return null; -+ } -+ -+ try { -+ double closestDistSq = -1.0; -+ LivingEntity closest = null; -+ -+ for (int i = 0; i < entities.size(); i++) { -+ try { -+ LivingEntity entity = entities.get(i); -+ if (entity == null || entity.isRemoved() || !entity.isAlive()) { -+ continue; -+ } -+ -+ if (conditions.test(level, source, entity)) { -+ double dx = entity.getX() - x; -+ double dy = entity.getY() - y; -+ double dz = entity.getZ() - z; -+ double distSq = dx * dx + dy * dy + dz * dz; -+ -+ if (closestDistSq == -1.0 || distSq < closestDistSq) { -+ closestDistSq = distSq; -+ closest = entity; -+ } -+ } -+ } catch (IndexOutOfBoundsException e) { -+ break; -+ } catch (Exception e) { -+ System.err.println("Error processing entity in findNearestEntitySafely: " + e.getMessage()); -+ continue; -+ } -+ } -+ -+ return closest; -+ } catch (Exception e) { -+ System.err.println("Error in findNearestEntitySafely: " + e.getMessage()); -+ return null; -+ } -+ } -+ -+ @Nullable -+ private Player findNearestPlayerSafely( -+ TargetingConditions conditions, -+ Mob source, -+ double x, -+ double y, -+ double z, -+ ServerLevel level) { -+ -+ if (level == null) { -+ return null; -+ } -+ -+ try { -+ java.util.List players = level.players(); -+ if (players == null || players.isEmpty()) { -+ return null; -+ } -+ -+ double closestDistSq = -1.0; -+ Player closest = null; -+ -+ for (int i = 0; i < players.size(); i++) { -+ try { -+ Player player = players.get(i); -+ if (player == null || player.isRemoved() || !player.isAlive()) { -+ continue; -+ } -+ -+ if (conditions.test(level, source, player)) { -+ double dx = player.getX() - x; -+ double dy = player.getY() - y; -+ double dz = player.getZ() - z; -+ double distSq = dx * dx + dy * dy + dz * dz; -+ -+ if (closestDistSq == -1.0 || distSq < closestDistSq) { -+ closestDistSq = distSq; -+ closest = player; -+ } -+ } -+ } catch (IndexOutOfBoundsException e) { -+ break; -+ } catch (Exception e) { -+ System.err.println("Error processing player in findNearestPlayerSafely: " + e.getMessage()); -+ continue; -+ } -+ } -+ -+ return closest; -+ } catch (Exception e) { -+ System.err.println("Error in findNearestPlayerSafely: " + e.getMessage()); -+ return null; -+ } -+ } -+ -+ // Synchronous fallback method -+ private void findTargetSync() { -+ try { -+ ServerLevel serverLevel = getServerLevel(this.mob); -+ if (serverLevel == null) { -+ return; -+ } -+ -+ if (this.targetType != Player.class && this.targetType != ServerPlayer.class) { -+ try { -+ this.target = serverLevel.getNearestEntity( -+ this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true), -+ this.getTargetConditions(), -+ this.mob, -+ this.mob.getX(), -+ this.mob.getEyeY(), -+ this.mob.getZ() -+ ); -+ } catch (Exception e) { -+ System.err.println("Error in sync entity finding: " + e.getMessage()); -+ this.target = null; -+ } -+ } else { -+ try { -+ this.target = serverLevel.getNearestPlayer(this.getTargetConditions(), this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); -+ } catch (Exception e) { -+ System.err.println("Error in sync player finding: " + e.getMessage()); -+ this.target = null; -+ } -+ } -+ } catch (Exception e) { -+ System.err.println("Error in findTargetSync: " + e.getMessage()); -+ this.target = null; -+ } -+ } -+ // Leaf end - Async Target Finding -+ - @Override - public void start() { -- this.mob.setTarget(this.target, this.target instanceof ServerPlayer ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true); // CraftBukkit - reason -+ // Leaf start - Async Target Finding -+ LivingEntity targetEntity = this.target; -+ if (targetEntity != null && !targetEntity.isRemoved() && targetEntity.isAlive()) { -+ try { -+ this.mob.setTarget(targetEntity, targetEntity instanceof ServerPlayer ? -+ org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : -+ org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true); -+ } catch (Exception e) { -+ System.err.println("Error in setTarget: " + e.getMessage()); -+ this.target = null; -+ } -+ } -+ // Leaf end - Async Target Finding - super.start(); - } - diff --git a/leaf-server/minecraft-patches/features/0156-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0156-Async-target-finding.patch new file mode 100644 index 00000000..e03e8c9f --- /dev/null +++ b/leaf-server/minecraft-patches/features/0156-Async-target-finding.patch @@ -0,0 +1,437 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sat, 29 Mar 2025 13:40:46 +0100 +Subject: [PATCH] Async target finding + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 24926aa7ed5c78b235659daf18b224b14beb744c..64d5cebd488892c93f07b938ce8dc3e99fddcdad 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1088,6 +1088,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop extends TargetG + protected final int randomInterval; + @Nullable + protected LivingEntity target; +- protected TargetingConditions targetConditions; ++ @Nullable protected TargetingConditions.Selector selector; // Leaf - create TargetingConditions instead of reusing it ++ // Leaf start - Async Target Finding ++ private static final org.apache.logging.log4j.Logger LOGGER = org.apache.logging.log4j.LogManager.getLogger("Leaf Async Target Lookup"); ++ // Single thread executor to prevent overwhelming the server ++ public static final java.util.concurrent.ExecutorService TARGET_EXECUTOR = java.util.concurrent.Executors.newSingleThreadExecutor( ++ new com.google.common.util.concurrent.ThreadFactoryBuilder() ++ .setNameFormat("Leaf Async Target Finding Thread") ++ .setDaemon(true) ++ .setPriority(Thread.NORM_PRIORITY - 2) ++ .build()); ++ ++ // Flag to track if a search is in progress ++ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); ++ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); ++ // Leaf end - Async Target Finding + + public NearestAttackableTargetGoal(Mob mob, Class targetType, boolean mustSee) { + this(mob, targetType, 10, mustSee, false, null); +@@ -36,19 +50,45 @@ public class NearestAttackableTargetGoal extends TargetG + ) { + super(mob, mustSee, mustReach); + this.targetType = targetType; +- this.randomInterval = reducedTickDelay(interval); ++ // Leaf start - update every tick ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ this.randomInterval = interval; ++ } else { ++ this.randomInterval = reducedTickDelay(interval); ++ } ++ // Leaf end - update every tick + this.setFlags(EnumSet.of(Goal.Flag.TARGET)); +- this.targetConditions = TargetingConditions.forCombat().range(this.getFollowDistance()).selector(selector); ++ this.selector = selector; // Leaf + } + + @Override + public boolean canUse() { ++ // Leaf start - Async target finding ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ LivingEntity t = pendingTarget.getAcquire(); ++ if (t != null) { ++ pendingTarget.setRelease(null); ++ ServerLevel serverLevel = getServerLevel(this.mob); ++ if (serverLevel != null && t.isAlive() && this.getTargetConditions().test(serverLevel, this.mob, t)) { ++ this.target = t; ++ } ++ return true; ++ } ++ } + if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) { + return false; ++ } else if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ this.findTargetAsync(); ++ ServerLevel serverLevel = getServerLevel(this.mob); ++ if (this.target != null && !(serverLevel != null && this.target.isAlive() && this.getTargetConditions().test(serverLevel, this.mob, this.target))) { ++ this.target = null; ++ } ++ return false; + } else { + this.findTarget(); + return this.target != null; + } ++ // Leaf end - Async target finding + } + + protected AABB getTargetSearchArea(double targetDistance) { +@@ -71,6 +111,176 @@ public class NearestAttackableTargetGoal extends TargetG + } + } + ++ // Leaf start - find target async ++ protected void findTargetAsync() { ++ if (isSearching.getAcquire()) { ++ return; ++ } ++ isSearching.setRelease(true); ++ ++ // Capture mutable state to avoid race conditions ++ final Mob mob = this.mob; ++ ++ // Safety check ++ if (mob == null || mob.isRemoved() || !mob.isAlive()) { ++ isSearching.setRelease(false); ++ return; ++ } ++ ++ final double x = mob.getX(); ++ final double y = mob.getEyeY(); ++ final double z = mob.getZ(); ++ final double followDistance = this.getFollowDistance(); ++ final TargetingConditions targetConditions = this.getTargetConditions(); ++ final Class targetType = this.targetType; ++ final double maxDistSqr = followDistance * followDistance; ++ final AABB targetSearch = getTargetSearchArea(this.getFollowDistance()); ++ ++ TARGET_EXECUTOR.execute(() -> { ++ try { ++ ServerLevel serverLevel = getServerLevel(mob); ++ if (serverLevel == null) { ++ return; ++ } ++ if (mob.isRemoved() || !mob.isAlive()) { ++ return; ++ } ++ ++ try { ++ if (targetType != Player.class && targetType != ServerPlayer.class) { ++ java.util.List entities; ++ try { ++ entities = mob.level().getEntitiesOfClass(targetType, targetSearch, entity -> entity != null && entity != mob && !entity.isRemoved() && entity.isAlive()); ++ } catch (Exception e) { ++ LOGGER.warn("Error getting entities", e); ++ return; ++ } ++ ++ if (entities != null && !entities.isEmpty()) { ++ var result = findNearestEntitySafely(entities, targetConditions, mob, x, y, z, serverLevel, maxDistSqr); ++ pendingTarget.setRelease(result); ++ } ++ } else { ++ var result = findNearestPlayerSafely(targetConditions, mob, x, y, z, serverLevel); ++ pendingTarget.setRelease(result); ++ } ++ } catch (Exception e) { ++ LOGGER.warn("Error finding entities in async target finder", e); ++ } ++ } catch (Exception e) { ++ LOGGER.warn("Error during async target finding", e); ++ } finally { ++ isSearching.setRelease(false); ++ } ++ }); ++ } ++ ++ @Nullable ++ private LivingEntity findNearestEntitySafely( ++ java.util.List entities, ++ TargetingConditions conditions, ++ Mob source, ++ double x, ++ double y, ++ double z, ++ ServerLevel level, ++ double maxDistSqr) { ++ ++ if (entities == null || entities.isEmpty() || level == null) { ++ return null; ++ } ++ ++ try { ++ double closestDistSq = maxDistSqr; ++ LivingEntity closest = null; ++ ++ for (int i = 0; i < entities.size(); i++) { ++ try { ++ LivingEntity entity = entities.get(i); ++ if (entity == null || entity == source || entity.isRemoved() || !entity.isAlive()) { ++ continue; ++ } ++ ++ if (conditions.test(level, source, entity)) { ++ double dx = entity.getX() - x; ++ double dy = entity.getY() - y; ++ double dz = entity.getZ() - z; ++ double distSq = dx * dx + dy * dy + dz * dz; ++ ++ if (distSq < closestDistSq) { ++ closestDistSq = distSq; ++ closest = entity; ++ } ++ } ++ } catch (IndexOutOfBoundsException e) { ++ break; ++ } catch (Exception e) { ++ LOGGER.warn("Error processing entity in findNearestEntitySafely", e); ++ } ++ } ++ ++ return closest; ++ } catch (Exception e) { ++ LOGGER.warn("Error in findNearestEntitySafely", e); ++ return null; ++ } ++ } ++ ++ @Nullable ++ private Player findNearestPlayerSafely( ++ TargetingConditions conditions, ++ Mob source, ++ double x, ++ double y, ++ double z, ++ ServerLevel level) { ++ ++ if (level == null) { ++ return null; ++ } ++ ++ try { ++ java.util.List players = level.players(); ++ if (players == null || players.isEmpty()) { ++ return null; ++ } ++ ++ double closestDistSq = -1.0; ++ Player closest = null; ++ ++ for (int i = 0; i < players.size(); i++) { ++ try { ++ Player player = players.get(i); ++ if (player == null || player.isRemoved() || !player.isAlive()) { ++ continue; ++ } ++ ++ if (conditions.test(level, source, player)) { ++ double dx = player.getX() - x; ++ double dy = player.getY() - y; ++ double dz = player.getZ() - z; ++ double distSq = dx * dx + dy * dy + dz * dz; ++ ++ if (closestDistSq == -1.0 || distSq < closestDistSq) { ++ closestDistSq = distSq; ++ closest = player; ++ } ++ } ++ } catch (IndexOutOfBoundsException e) { ++ break; ++ } catch (Exception e) { ++ System.err.println("Error processing player in findNearestPlayerSafely: " + e.getMessage()); ++ } ++ } ++ ++ return closest; ++ } catch (Exception e) { ++ System.err.println("Error in findNearestPlayerSafely: " + e.getMessage()); ++ return null; ++ } ++ } ++ // Leaf end - find target async ++ + @Override + public void start() { + this.mob.setTarget(this.target, this.target instanceof ServerPlayer ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true); // CraftBukkit - reason +@@ -81,7 +291,16 @@ public class NearestAttackableTargetGoal extends TargetG + this.target = target; + } + +- private TargetingConditions getTargetConditions() { +- return this.targetConditions.range(this.getFollowDistance()); ++ // Leaf start ++ protected TargetingConditions getTargetConditions() { ++ return TargetingConditions.forCombat().range(this.getFollowDistance()).selector(this.selector); ++ } ++ // Leaf end ++ ++ // Leaf start - update every tick ++ @Override ++ public boolean requiresUpdateEveryTick() { ++ return org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled; + } ++ // Leaf end - update every tick + } +diff --git a/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java +index abf57494950f55bbd75f335f26736cb9e703c197..683722fbd07fcae9cdd72928ed25fd084202b57e 100644 +--- a/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java ++++ b/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java +@@ -20,6 +20,15 @@ public class NonTameRandomTargetGoal extends NearestAtta + + @Override + public boolean canContinueToUse() { +- return this.targetConditions != null ? this.targetConditions.test(getServerLevel(this.mob), this.mob, this.target) : super.canContinueToUse(); ++ // Leaf start ++ if (this.target == null || !this.target.isAlive() || this.target.isRemoved()) { ++ return false; ++ } ++ var serverLevel = getServerLevel(this.mob); ++ if (serverLevel == null) { ++ return false; ++ } ++ return this.getTargetConditions().test(serverLevel, this.mob, this.target) && super.canContinueToUse(); ++ // Leaf end + } + } +diff --git a/net/minecraft/world/entity/ai/sensing/Sensing.java b/net/minecraft/world/entity/ai/sensing/Sensing.java +index 002d3c0d8b1107a275020d5c582c37e9a5c536ee..3f8c18f4040f4929df79ba85906330b150720cb0 100644 +--- a/net/minecraft/world/entity/ai/sensing/Sensing.java ++++ b/net/minecraft/world/entity/ai/sensing/Sensing.java +@@ -32,9 +32,21 @@ public class Sensing { + // Gale end - Petal - reduce line of sight updates - expiring entity id lists + } + ++ // Leaf start - async target finding + public void tick() { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ synchronized (this) { ++ tick1(); ++ } ++ } else { ++ tick1(); ++ } ++ } ++ ++ private void tick1() { ++ // Leaf end - async target finding + if (this.expiring == null) { // Gale - Petal - reduce line of sight updates +- this.seen.clear(); ++ this.seen.clear(); + // Gale start - Petal - reduce line of sight updates + } else { + var expiringNow = this.expiring[this.nextToExpireIndex]; +@@ -62,7 +74,19 @@ public class Sensing { + // Gale end - Petal - reduce line of sight updates + } + ++ // Leaf start - async target finding + public boolean hasLineOfSight(Entity entity) { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ synchronized (this) { ++ return hasLineOfSight1(entity); ++ } ++ } else { ++ return hasLineOfSight1(entity); ++ } ++ } ++ ++ private boolean hasLineOfSight1(Entity entity) { ++ // Leaf end - async target finding + int id = entity.getId(); + // Gale start - Petal - reduce line of sight cache lookups - merge sets + int cached = this.seen.get(id); +diff --git a/net/minecraft/world/entity/animal/Fox.java b/net/minecraft/world/entity/animal/Fox.java +index 90452f0945e761077608692877677f522d38bccd..736eea50b4460041242bdd4b7191d67d7dd4524d 100644 +--- a/net/minecraft/world/entity/animal/Fox.java ++++ b/net/minecraft/world/entity/animal/Fox.java +@@ -849,13 +849,18 @@ public class Fox extends Animal implements VariantHolder { + return false; + } else { + ServerLevel serverLevel = getServerLevel(Fox.this.level()); ++ // Leaf start ++ if (serverLevel == null) { ++ return false; ++ } ++ // Leaf end + + for (UUID uuid : Fox.this.getTrustedUUIDs()) { + if (serverLevel.getEntity(uuid) instanceof LivingEntity livingEntity) { + this.trustedLastHurt = livingEntity; + this.trustedLastHurtBy = livingEntity.getLastHurtByMob(); + int lastHurtByMobTimestamp = livingEntity.getLastHurtByMobTimestamp(); +- return lastHurtByMobTimestamp != this.timestamp && this.canAttack(this.trustedLastHurtBy, this.targetConditions); ++ return lastHurtByMobTimestamp != this.timestamp && this.canAttack(this.trustedLastHurtBy, this.getTargetConditions()); // Leaf + } + } + From 05d64d7eb7d651fd81d842c039f5bc5d5d2e4bcf Mon Sep 17 00:00:00 2001 From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> Date: Mon, 21 Apr 2025 06:17:56 +1400 Subject: [PATCH 05/45] Add nullability check before caching profile result --- .../features/0048-Cache-player-profileResult.patch | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0048-Cache-player-profileResult.patch b/leaf-server/minecraft-patches/features/0048-Cache-player-profileResult.patch index 22f7af70..2b4d7548 100644 --- a/leaf-server/minecraft-patches/features/0048-Cache-player-profileResult.patch +++ b/leaf-server/minecraft-patches/features/0048-Cache-player-profileResult.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Cache player profileResult diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 069477e524a28b20a0289221858bdc802704a890..21ecbbdd97204477dadd2ade1d93f64cf91c7dfe 100644 +index 069477e524a28b20a0289221858bdc802704a890..e9ce273812259627b61824ca4ffe83d301a4d946 100644 --- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -71,6 +71,11 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, @@ -20,7 +20,7 @@ index 069477e524a28b20a0289221858bdc802704a890..21ecbbdd97204477dadd2ade1d93f64c public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) { this.server = server; -@@ -304,9 +309,23 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, +@@ -304,9 +309,25 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, String string1 = Objects.requireNonNull(ServerLoginPacketListenerImpl.this.requestedUsername, "Player name not initialized"); try { @@ -36,7 +36,9 @@ index 069477e524a28b20a0289221858bdc802704a890..21ecbbdd97204477dadd2ade1d93f64c + profileResult = ServerLoginPacketListenerImpl.this.server + .getSessionService() + .hasJoinedServer(string1, string, this.getAddress()); -+ playerProfileResultCache.put(string1, profileResult); ++ if (profileResult != null) { ++ playerProfileResultCache.put(string1, profileResult); ++ } + } + } else { + profileResult = ServerLoginPacketListenerImpl.this.server From 65d24439b8f8f51d47eab7f88cdb314f6952684a Mon Sep 17 00:00:00 2001 From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> Date: Mon, 21 Apr 2025 06:40:31 +1400 Subject: [PATCH 06/45] Mark FasterRandomSource direct impl as static final --- .../org/dreeam/leaf/util/math/random/FasterRandomSource.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/math/random/FasterRandomSource.java b/leaf-server/src/main/java/org/dreeam/leaf/util/math/random/FasterRandomSource.java index 46c9c1e7..ee6d5dc5 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/util/math/random/FasterRandomSource.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/math/random/FasterRandomSource.java @@ -20,20 +20,18 @@ public class FasterRandomSource implements BitRandomSource { private static final RandomGeneratorFactory RANDOM_GENERATOR_FACTORY = RandomGeneratorFactory.of(FastRNG.randomGenerator); private static final boolean isSplittableGenerator = RANDOM_GENERATOR_FACTORY.isSplittable(); private long seed; - private boolean useDirectImpl; + private static final boolean useDirectImpl = FastRNG.useDirectImpl; private RandomGenerator randomGenerator; public static final FasterRandomSource SHARED_INSTANCE = new FasterRandomSource(ThreadLocalRandom.current().nextLong()); public FasterRandomSource(long seed) { this.seed = seed; this.randomGenerator = RANDOM_GENERATOR_FACTORY.create(seed); - this.useDirectImpl = FastRNG.useDirectImpl; // Get the value from config } private FasterRandomSource(long seed, RandomGenerator.SplittableGenerator randomGenerator) { this.seed = seed; this.randomGenerator = randomGenerator; - this.useDirectImpl = FastRNG.useDirectImpl; } @Override @@ -59,7 +57,6 @@ public class FasterRandomSource implements BitRandomSource { @Override public final int next(int bits) { if (useDirectImpl) { - // Direct return (int) ((seed = seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> (INT_BITS - bits)); } From e82999dd18e274b061880c845e165ede07e72f7c Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Mon, 21 Apr 2025 12:30:15 -0400 Subject: [PATCH 07/45] Updated Upstream (Paper/Gale) Upstream has released updates that appear to apply and compile correctly Paper Changes: PaperMC/Paper@07679026 CraftBlock - fix applyBoneMeal false result (#12407) PaperMC/Paper@a838a886 Add PlayerRespawnEvent#isMissingRespawnBlock (#12422) Gale Changes: Dreeam-qwq/Gale@3c153f5f Updated Upstream (Paper) --- gradle.properties | 2 +- .../0007-Purpur-Server-Minecraft-Changes.patch | 18 +++++++++--------- .../0044-Improve-Purpur-AFK-system.patch | 10 +++++----- .../features/0076-Fix-MC-119417.patch | 4 ++-- .../features/0100-Smooth-teleport-config.patch | 8 ++++---- ...e-eligible-players-for-despawn-checks.patch | 6 +++--- ...8-SparklyPaper-Parallel-world-ticking.patch | 8 ++++---- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/gradle.properties b/gradle.properties index 3418093f..387a6685 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ group=cn.dreeam.leaf mcVersion=1.21.4 version=1.21.4-R0.1-SNAPSHOT -galeCommit=4ec6e8a5964d1ebdfc9b118c9564a506605aa6bf +galeCommit=3c153f5f8e7b4f4ae122163bd2ec238bab532e89 org.gradle.configuration-cache=true org.gradle.caching=true diff --git a/leaf-server/minecraft-patches/features/0007-Purpur-Server-Minecraft-Changes.patch b/leaf-server/minecraft-patches/features/0007-Purpur-Server-Minecraft-Changes.patch index 43a468d1..90075701 100644 --- a/leaf-server/minecraft-patches/features/0007-Purpur-Server-Minecraft-Changes.patch +++ b/leaf-server/minecraft-patches/features/0007-Purpur-Server-Minecraft-Changes.patch @@ -944,7 +944,7 @@ index 101f1a87a5fe920b57a5179da41cc91d88afa32e..b49dd636e730f0c5b609df68ee51bcd1 } // Paper end - Fix merchant inventory not closing on entity removal diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index f51d699461f5aef81e1187a4068c78f844e83878..668c173dc69b4ab77d91666dc2059f2b9afd7ee7 100644 +index 50db1221d672d36b58b65177a746d365f7cdc386..d4098a1666f0eb7060caaf93f5c5a13356b4ce56 100644 --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java @@ -399,6 +399,10 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc @@ -1022,7 +1022,7 @@ index f51d699461f5aef81e1187a4068c78f844e83878..668c173dc69b4ab77d91666dc2059f2b Entity entity = damageSource.getEntity(); if (!( // Paper - split the if statement. If below statement is false, hurtServer would not have been evaluated. Return false. !(entity instanceof Player player && !this.canHarmPlayer(player)) -@@ -1448,6 +1481,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -1449,6 +1482,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc serverLevel.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); this.unsetRemoved(); // CraftBukkit end @@ -1030,7 +1030,7 @@ index f51d699461f5aef81e1187a4068c78f844e83878..668c173dc69b4ab77d91666dc2059f2b this.setServerLevel(level); this.connection.internalTeleport(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives()); // CraftBukkit - use internal teleport without event this.connection.resetPosition(); -@@ -1565,7 +1599,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -1566,7 +1600,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc new AABB(vec3.x() - 8.0, vec3.y() - 5.0, vec3.z() - 8.0, vec3.x() + 8.0, vec3.y() + 5.0, vec3.z() + 8.0), monster -> monster.isPreventingPlayerRest(this.serverLevel(), this) ); @@ -1039,7 +1039,7 @@ index f51d699461f5aef81e1187a4068c78f844e83878..668c173dc69b4ab77d91666dc2059f2b return Either.left(Player.BedSleepingProblem.NOT_SAFE); } } -@@ -1602,7 +1636,19 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -1603,7 +1637,19 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc CriteriaTriggers.SLEPT_IN_BED.trigger(this); }); if (!this.serverLevel().canSleepThroughNights()) { @@ -1060,7 +1060,7 @@ index f51d699461f5aef81e1187a4068c78f844e83878..668c173dc69b4ab77d91666dc2059f2b } ((ServerLevel)this.level()).updateSleepingPlayerList(); -@@ -1710,6 +1756,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -1711,6 +1757,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc @Override public void openTextEdit(SignBlockEntity signEntity, boolean isFrontText) { @@ -1068,7 +1068,7 @@ index f51d699461f5aef81e1187a4068c78f844e83878..668c173dc69b4ab77d91666dc2059f2b this.connection.send(new ClientboundBlockUpdatePacket(this.level(), signEntity.getBlockPos())); this.connection.send(new ClientboundOpenSignEditorPacket(signEntity.getBlockPos(), isFrontText)); } -@@ -2015,6 +2062,26 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -2016,6 +2063,26 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc this.lastSentExp = -1; // CraftBukkit - Added to reset } @@ -1095,7 +1095,7 @@ index f51d699461f5aef81e1187a4068c78f844e83878..668c173dc69b4ab77d91666dc2059f2b @Override public void displayClientMessage(Component chatComponent, boolean actionBar) { this.sendSystemMessage(chatComponent, actionBar); -@@ -2242,6 +2309,20 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -2243,6 +2310,20 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc ); } @@ -1116,7 +1116,7 @@ index f51d699461f5aef81e1187a4068c78f844e83878..668c173dc69b4ab77d91666dc2059f2b public void sendSystemMessage(Component mesage) { this.sendSystemMessage(mesage, false); } -@@ -2380,8 +2461,68 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -2381,8 +2462,68 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc public void resetLastActionTime() { this.lastActionTime = Util.getMillis(); @@ -1185,7 +1185,7 @@ index f51d699461f5aef81e1187a4068c78f844e83878..668c173dc69b4ab77d91666dc2059f2b public ServerStatsCounter getStats() { return this.stats; } -@@ -3085,4 +3226,56 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -3086,4 +3227,56 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc return (org.bukkit.craftbukkit.entity.CraftPlayer) super.getBukkitEntity(); } // CraftBukkit end diff --git a/leaf-server/minecraft-patches/features/0044-Improve-Purpur-AFK-system.patch b/leaf-server/minecraft-patches/features/0044-Improve-Purpur-AFK-system.patch index 694c685e..f4f9c5a1 100644 --- a/leaf-server/minecraft-patches/features/0044-Improve-Purpur-AFK-system.patch +++ b/leaf-server/minecraft-patches/features/0044-Improve-Purpur-AFK-system.patch @@ -19,10 +19,10 @@ index ee7bdfd8f9da8d5989c9cc25f8cbcc94640361c5..8b9374ee6df71228bb8ea22661622a15 org.purpurmc.purpur.command.DemoCommand.register(this.dispatcher); // Purpur - Add demo command org.purpurmc.purpur.command.PingCommand.register(this.dispatcher); // Purpur - Add ping command diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index f89d28595fa9ca12e414f7b3cc86085ff0769e72..29fd9cea422e1ee09f6983a9b72847fbeaf57e3d 100644 +index 6ea52b077cd867528edcea2f8c5d1f925f2f304f..ab302d642b1d7b6f59b9ee32a0c514d0abf8d1b1 100644 --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java -@@ -2467,6 +2467,10 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -2468,6 +2468,10 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc // Purpur start - AFK API private boolean isAfk = false; @@ -33,7 +33,7 @@ index f89d28595fa9ca12e414f7b3cc86085ff0769e72..29fd9cea422e1ee09f6983a9b72847fb @Override public void setAfk(boolean afk) { -@@ -2504,6 +2508,18 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -2505,6 +2509,18 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc String prefix = (split.length > 0 ? split[0] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix, ""); String suffix = (split.length > 1 ? split[1] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, ""); if (afk) { @@ -53,7 +53,7 @@ index f89d28595fa9ca12e414f7b3cc86085ff0769e72..29fd9cea422e1ee09f6983a9b72847fb } else { getBukkitEntity().setPlayerListName(prefix + scoreboardName + suffix, true); diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index f10946b641e0af144ca653850c5d5292c0095412..90535f96479c936c516949ffc5def9e63b0daa95 100644 +index 594ff2cf2a1f9eea2fb99997383af6d78dc424fb..ddd8a7a9b33618979ff0b69a95ce8041053082e8 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -2272,6 +2272,7 @@ public class ServerGamePacketListenerImpl @@ -164,7 +164,7 @@ index 386423589443051b8c461926c570352dd612a051..84f9a8a606ede4ef8361a2683d775121 player.awardStat(Stats.LEAVE_GAME); // CraftBukkit start - Quitting must be before we do final save of data, in case plugins need to modify it diff --git a/org/purpurmc/purpur/PurpurConfig.java b/org/purpurmc/purpur/PurpurConfig.java -index b8c8806789bd0060cd3faee5815bbf25c8715a9b..c726406ac67980f0403cc524d96f08916218667a 100644 +index c0cde1461c0e3ec63bf77cc5056f8c637f307d4a..e5c3b033f5fab18a92fd6bb48eecf141b5a9db26 100644 --- a/org/purpurmc/purpur/PurpurConfig.java +++ b/org/purpurmc/purpur/PurpurConfig.java @@ -175,6 +175,11 @@ public class PurpurConfig { diff --git a/leaf-server/minecraft-patches/features/0076-Fix-MC-119417.patch b/leaf-server/minecraft-patches/features/0076-Fix-MC-119417.patch index 3e4795e9..68e9e9c0 100644 --- a/leaf-server/minecraft-patches/features/0076-Fix-MC-119417.patch +++ b/leaf-server/minecraft-patches/features/0076-Fix-MC-119417.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Fix-MC-119417 Related MC issue: https://bugs.mojang.com/browse/MC-119417 diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index 29fd9cea422e1ee09f6983a9b72847fbeaf57e3d..2a3c38cd8e31f73eca2508ad94e46ace980de50c 100644 +index ab302d642b1d7b6f59b9ee32a0c514d0abf8d1b1..1957bd89e3ed34714c3633a27df63205a4b50b6b 100644 --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java -@@ -2267,6 +2267,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -2268,6 +2268,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, gameMode.getId())); if (gameMode == GameType.SPECTATOR) { this.removeEntitiesOnShoulder(); diff --git a/leaf-server/minecraft-patches/features/0100-Smooth-teleport-config.patch b/leaf-server/minecraft-patches/features/0100-Smooth-teleport-config.patch index e82a75a8..27419a08 100644 --- a/leaf-server/minecraft-patches/features/0100-Smooth-teleport-config.patch +++ b/leaf-server/minecraft-patches/features/0100-Smooth-teleport-config.patch @@ -9,10 +9,10 @@ happen but the visual "refresh" of a world change is hidden. Depending on the de this can act as a "smooth teleport" to a world if the new world is very similar looking to the old one. diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index 2a3c38cd8e31f73eca2508ad94e46ace980de50c..336215befa7eb4b20fc86f224a019080cea70113 100644 +index 1957bd89e3ed34714c3633a27df63205a4b50b6b..5ae1a69893cc9bee7126607e90fbaed1e9a9af06 100644 --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java -@@ -1475,6 +1475,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -1476,6 +1476,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc LevelData worlddata = level.getLevelData(); this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(level), (byte) 3)); @@ -20,7 +20,7 @@ index 2a3c38cd8e31f73eca2508ad94e46ace980de50c..336215befa7eb4b20fc86f224a019080 this.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); PlayerList playerList = this.server.getPlayerList(); -@@ -1484,7 +1485,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -1485,7 +1486,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc // CraftBukkit end this.portalPos = io.papermc.paper.util.MCUtil.toBlockPosition(exit); // Purpur - Fix stuck in portals this.setServerLevel(level); @@ -30,7 +30,7 @@ index 2a3c38cd8e31f73eca2508ad94e46ace980de50c..336215befa7eb4b20fc86f224a019080 level.addDuringTeleport(this); this.triggerDimensionChangeTriggers(serverLevel); diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java -index bda8e9e23f63a6eb27f71fc795ad6495e62f00f2..f59662da0bbfe0e768c4ac5c7491d13263ac5cac 100644 +index 75fb49f1596f475278d12c8c7aea9ad4952b6056..b17c8a2f5294ac28cc05fb05c84a041b2c6c8721 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java @@ -955,11 +955,11 @@ public abstract class PlayerList { 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 index cd9d210f..0d13dc0c 100644 --- 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 @@ -5,7 +5,7 @@ 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 20ded514a74652685b2f785c7fe5fda19e36b2a5..5da07e22ef9dac7baca9d8450b7eae3f6fa141b1 100644 +index a9e7424bb55266c5e04c56dcf598ce7d149eeb21..ae5d3de44fb710b48fdabf04f5e706df1f9889b7 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 @@ -38,10 +38,10 @@ index 20ded514a74652685b2f785c7fe5fda19e36b2a5..5da07e22ef9dac7baca9d8450b7eae3f .forEach( entity -> { diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index 336215befa7eb4b20fc86f224a019080cea70113..e2b15968e89a532ec21c786f41b7f9322fd65a04 100644 +index 5ae1a69893cc9bee7126607e90fbaed1e9a9af06..fefaab58da149b082a4d1e3bed9ec84ae8488d45 100644 --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java -@@ -1576,6 +1576,13 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -1577,6 +1577,13 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc this.containerMenu.broadcastChanges(); } diff --git a/leaf-server/minecraft-patches/features/0138-SparklyPaper-Parallel-world-ticking.patch b/leaf-server/minecraft-patches/features/0138-SparklyPaper-Parallel-world-ticking.patch index 698bc071..b8cc4514 100644 --- a/leaf-server/minecraft-patches/features/0138-SparklyPaper-Parallel-world-ticking.patch +++ b/leaf-server/minecraft-patches/features/0138-SparklyPaper-Parallel-world-ticking.patch @@ -650,7 +650,7 @@ index ae5d3de44fb710b48fdabf04f5e706df1f9889b7..31abf2da10bc9b4b7825ed4b3d4e9da5 // Paper start - extra debug info if (entity.valid) { diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index e2b15968e89a532ec21c786f41b7f9322fd65a04..915498dcb08a678256e6cc1659642d6126851365 100644 +index fefaab58da149b082a4d1e3bed9ec84ae8488d45..9100da3fe4e478cea7198cb4e028fcefccb3eb3c 100644 --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java @@ -434,6 +434,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc @@ -669,7 +669,7 @@ index e2b15968e89a532ec21c786f41b7f9322fd65a04..915498dcb08a678256e6cc1659642d61 // CraftBukkit start if (this.joining) { this.joining = false; -@@ -1454,6 +1456,8 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -1455,6 +1457,8 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc teleportTransition.postTeleportTransition().onTransition(this); return this; } else { @@ -678,7 +678,7 @@ index e2b15968e89a532ec21c786f41b7f9322fd65a04..915498dcb08a678256e6cc1659642d61 // CraftBukkit start /* this.isChangingDimension = true; -@@ -1825,6 +1829,12 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -1826,6 +1830,12 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc return OptionalInt.empty(); } else { // CraftBukkit start @@ -691,7 +691,7 @@ index e2b15968e89a532ec21c786f41b7f9322fd65a04..915498dcb08a678256e6cc1659642d61 this.containerMenu = abstractContainerMenu; // Moved up if (!this.isImmobile()) this.connection -@@ -1889,6 +1899,11 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -1890,6 +1900,11 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc } @Override public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { From 83e9043a459bd9acf71283f40676c4a49b4dae0b Mon Sep 17 00:00:00 2001 From: Taiyou <77050201+Taiyou06@users.noreply.github.com> Date: Mon, 21 Apr 2025 22:49:33 +0200 Subject: [PATCH 08/45] Couple fixes and improvements (#287) * a lot of cleanup and new chunk changes * perf: Head Node Hit Optimization * part 1: reworked-reworked ChunkHolderManager * part 2: speeeeeeeeeeeeeeeeeeeeeeeeeeed * Optimise MobEffectUtil#getDigSpeedAmplification * optimize chunk unloads and cleanup a bit * fix :bee: * rewritten async target finding * extend the custom map usage --------- Co-authored-by: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> --- ...7-Improve-sorting-in-SortedArraySet.patch} | 0 .../features/0127-Optimize-AABB.patch | 78 - ... 0128-Make-removeIf-slightly-faster.patch} | 0 ...atch => 0129-Optimize-LinearPalette.patch} | 0 ...130-Slightly-optimized-VarInt-write.patch} | 0 ...te-ClientboundLightUpdatePacketData.patch} | 0 ...g.patch => 0132-Async-chunk-sending.patch} | 0 ...timizations-on-SerializableChunkData.patch | 84 - ...atch => 0133-Spawner-Configurations.patch} | 0 .../0134-Rework-ChunkHolderManager.patch | 134 -- ...SparklyPaper-Parallel-world-ticking.patch} | 8 +- ...-SparklyPaper-Track-each-world-MSPT.patch} | 0 ...lled-Projectile-Events-still-consum.patch} | 0 ...ndInteract-and-NearestVisibleLiving.patch} | 0 ...emove-streams-on-InsideBrownianWalk.patch} | 0 ...=> 0139-Use-BFS-on-getSlopeDistance.patch} | 0 ...r-PR-Throttle-failed-spawn-attempts.patch} | 10 +- ...BlockEntity-ticking-isRemoved-check.patch} | 0 ...2-Raytrace-AntiXray-SDK-integration.patch} | 0 ...timize-addOrUpdateTransientModifier.patch} | 0 ... => 0144-Optimize-ContextMap.create.patch} | 0 ...Micro-optimizations-for-random-tick.patch} | 0 ...n-updateConnectedPlayersWithinRange.patch} | 0 ...47-Remove-streams-on-PlayerDetector.patch} | 0 ...g.patch => 0148-Async-Block-Finding.patch} | 6 +- ...se-direct-iteration-on-Sensing.tick.patch} | 0 ...0-Optimise-non-flush-packet-sending.patch} | 0 ...unk-retrieving-in-entity-fluid-push.patch} | 0 ...-Null-handling-on-MultifaceSpreader.patch} | 0 ....patch => 0153-More-virtual-threads.patch} | 0 ...ntLong2ReferenceChainedHashTable-wit.patch | 148 ++ ....patch => 0155-Async-target-finding.patch} | 0 .../0156-Reworked-ChunkHolderManager.patch | 151 ++ ...timize-ThreadedTicketLevelPropagator.patch | 277 +++ ...bEffectUtil-getDigSpeedAmplification.patch | 29 + ...patch => 0159-Optimise-chunkUnloads.patch} | 167 +- ...currentLong2ReferenceChainedHashTable.java | 2102 +++++++++++++++++ 37 files changed, 2884 insertions(+), 310 deletions(-) rename leaf-server/minecraft-patches/features/{0128-Improve-sorting-in-SortedArraySet.patch => 0127-Improve-sorting-in-SortedArraySet.patch} (100%) delete mode 100644 leaf-server/minecraft-patches/features/0127-Optimize-AABB.patch rename leaf-server/minecraft-patches/features/{0129-Make-removeIf-slightly-faster.patch => 0128-Make-removeIf-slightly-faster.patch} (100%) rename leaf-server/minecraft-patches/features/{0130-Optimize-LinearPalette.patch => 0129-Optimize-LinearPalette.patch} (100%) rename leaf-server/minecraft-patches/features/{0131-Slightly-optimized-VarInt-write.patch => 0130-Slightly-optimized-VarInt-write.patch} (100%) rename leaf-server/minecraft-patches/features/{0132-Rewrite-ClientboundLightUpdatePacketData.patch => 0131-Rewrite-ClientboundLightUpdatePacketData.patch} (100%) rename leaf-server/minecraft-patches/features/{0136-Async-chunk-sending.patch => 0132-Async-chunk-sending.patch} (100%) delete mode 100644 leaf-server/minecraft-patches/features/0133-Some-Optimizations-on-SerializableChunkData.patch rename leaf-server/minecraft-patches/features/{0137-Spawner-Configurations.patch => 0133-Spawner-Configurations.patch} (100%) delete mode 100644 leaf-server/minecraft-patches/features/0134-Rework-ChunkHolderManager.patch rename leaf-server/minecraft-patches/features/{0138-SparklyPaper-Parallel-world-ticking.patch => 0134-SparklyPaper-Parallel-world-ticking.patch} (99%) rename leaf-server/minecraft-patches/features/{0139-SparklyPaper-Track-each-world-MSPT.patch => 0135-SparklyPaper-Track-each-world-MSPT.patch} (100%) rename leaf-server/minecraft-patches/features/{0140-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch => 0136-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch} (100%) rename leaf-server/minecraft-patches/features/{0141-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch => 0137-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch} (100%) rename leaf-server/minecraft-patches/features/{0142-Remove-streams-on-InsideBrownianWalk.patch => 0138-Remove-streams-on-InsideBrownianWalk.patch} (100%) rename leaf-server/minecraft-patches/features/{0143-Use-BFS-on-getSlopeDistance.patch => 0139-Use-BFS-on-getSlopeDistance.patch} (100%) rename leaf-server/minecraft-patches/features/{0144-Paper-PR-Throttle-failed-spawn-attempts.patch => 0140-Paper-PR-Throttle-failed-spawn-attempts.patch} (98%) rename leaf-server/minecraft-patches/features/{0145-Improve-BlockEntity-ticking-isRemoved-check.patch => 0141-Improve-BlockEntity-ticking-isRemoved-check.patch} (100%) rename leaf-server/minecraft-patches/features/{0146-Raytrace-AntiXray-SDK-integration.patch => 0142-Raytrace-AntiXray-SDK-integration.patch} (100%) rename leaf-server/minecraft-patches/features/{0147-Optimize-addOrUpdateTransientModifier.patch => 0143-Optimize-addOrUpdateTransientModifier.patch} (100%) rename leaf-server/minecraft-patches/features/{0148-Optimize-ContextMap.create.patch => 0144-Optimize-ContextMap.create.patch} (100%) rename leaf-server/minecraft-patches/features/{0149-Micro-optimizations-for-random-tick.patch => 0145-Micro-optimizations-for-random-tick.patch} (100%) rename leaf-server/minecraft-patches/features/{0150-Remove-streams-on-updateConnectedPlayersWithinRange.patch => 0146-Remove-streams-on-updateConnectedPlayersWithinRange.patch} (100%) rename leaf-server/minecraft-patches/features/{0151-Remove-streams-on-PlayerDetector.patch => 0147-Remove-streams-on-PlayerDetector.patch} (100%) rename leaf-server/minecraft-patches/features/{0152-Async-Block-Finding.patch => 0148-Async-Block-Finding.patch} (96%) rename leaf-server/minecraft-patches/features/{0153-Use-direct-iteration-on-Sensing.tick.patch => 0149-Use-direct-iteration-on-Sensing.tick.patch} (100%) rename leaf-server/minecraft-patches/features/{0154-Optimise-non-flush-packet-sending.patch => 0150-Optimise-non-flush-packet-sending.patch} (100%) rename leaf-server/minecraft-patches/features/{0155-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch => 0151-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch} (100%) rename leaf-server/minecraft-patches/features/{0157-Null-handling-on-MultifaceSpreader.patch => 0152-Null-handling-on-MultifaceSpreader.patch} (100%) rename leaf-server/minecraft-patches/features/{0158-More-virtual-threads.patch => 0153-More-virtual-threads.patch} (100%) create mode 100644 leaf-server/minecraft-patches/features/0154-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch rename leaf-server/minecraft-patches/features/{0156-Async-target-finding.patch => 0155-Async-target-finding.patch} (100%) create mode 100644 leaf-server/minecraft-patches/features/0156-Reworked-ChunkHolderManager.patch create mode 100644 leaf-server/minecraft-patches/features/0157-Optimize-ThreadedTicketLevelPropagator.patch create mode 100644 leaf-server/minecraft-patches/features/0158-Optimise-MobEffectUtil-getDigSpeedAmplification.patch rename leaf-server/minecraft-patches/features/{0135-Optimize-chunkUnload.patch => 0159-Optimise-chunkUnloads.patch} (51%) create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/util/map/spottedleaf/LeafConcurrentLong2ReferenceChainedHashTable.java diff --git a/leaf-server/minecraft-patches/features/0128-Improve-sorting-in-SortedArraySet.patch b/leaf-server/minecraft-patches/features/0127-Improve-sorting-in-SortedArraySet.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0128-Improve-sorting-in-SortedArraySet.patch rename to leaf-server/minecraft-patches/features/0127-Improve-sorting-in-SortedArraySet.patch diff --git a/leaf-server/minecraft-patches/features/0127-Optimize-AABB.patch b/leaf-server/minecraft-patches/features/0127-Optimize-AABB.patch deleted file mode 100644 index cfc2bc58..00000000 --- a/leaf-server/minecraft-patches/features/0127-Optimize-AABB.patch +++ /dev/null @@ -1,78 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Sun, 16 Feb 2025 19:03:23 +0100 -Subject: [PATCH] Optimize AABB - -Pretty minor stuff but, it improves AABB.intersect by around ~5% - -diff --git a/net/minecraft/world/phys/AABB.java b/net/minecraft/world/phys/AABB.java -index f64c04b32dd2d0fe143fc8bf9f498e52beb66a58..00daaff66bd26e9ca15a7eb4052ff38f9e662f7b 100644 ---- a/net/minecraft/world/phys/AABB.java -+++ b/net/minecraft/world/phys/AABB.java -@@ -220,13 +220,16 @@ public class AABB { - } - - public AABB intersect(AABB other) { -- double max = Math.max(this.minX, other.minX); -- double max1 = Math.max(this.minY, other.minY); -- double max2 = Math.max(this.minZ, other.minZ); -- double min = Math.min(this.maxX, other.maxX); -- double min1 = Math.min(this.maxY, other.maxY); -- double min2 = Math.min(this.maxZ, other.maxZ); -- return new AABB(max, max1, max2, min, min1, min2); -+ // Leaf start - Optimize AABB -+ return new AABB( -+ this.minX > other.minX ? this.minX : other.minX, -+ this.minY > other.minY ? this.minY : other.minY, -+ this.minZ > other.minZ ? this.minZ : other.minZ, -+ this.maxX < other.maxX ? this.maxX : other.maxX, -+ this.maxY < other.maxY ? this.maxY : other.maxY, -+ this.maxZ < other.maxZ ? this.maxZ : other.maxZ -+ ); -+ // Leaf end - Optimize AABB - } - - public AABB minmax(AABB other) { -@@ -258,16 +261,39 @@ public class AABB { - } - - public boolean intersects(AABB other) { -- return this.intersects(other.minX, other.minY, other.minZ, other.maxX, other.maxY, other.maxZ); -+ // Leaf start - Optimize AABB -+ // Removed redundant method call overhead -+ return this.minX < other.maxX && -+ this.maxX > other.minX && -+ this.minY < other.maxY && -+ this.maxY > other.minY && -+ this.minZ < other.maxZ && -+ this.maxZ > other.minZ; -+ // Leaf end - Optimize AABB - } - - public boolean intersects(double x1, double y1, double z1, double x2, double y2, double z2) { -- return this.minX < x2 && this.maxX > x1 && this.minY < y2 && this.maxY > y1 && this.minZ < z2 && this.maxZ > z1; -+ // Leaf start - Optimize AABB -+ // No temporary variables needed, direct comparison -+ return this.minX < x2 && -+ this.maxX > x1 && -+ this.minY < y2 && -+ this.maxY > y1 && -+ this.minZ < z2 && -+ this.maxZ > z1; -+ // Leaf end - Optimize AABB - } - - public boolean intersects(Vec3 min, Vec3 max) { - return this.intersects( -- Math.min(min.x, max.x), Math.min(min.y, max.y), Math.min(min.z, max.z), Math.max(min.x, max.x), Math.max(min.y, max.y), Math.max(min.z, max.z) -+ // Leaf start - Optimize AABB -+ min.x < max.x ? min.x : max.x, -+ min.y < max.y ? min.y : max.y, -+ min.z < max.z ? min.z : max.z, -+ min.x > max.x ? min.x : max.x, -+ min.y > max.y ? min.y : max.y, -+ min.z > max.z ? min.z : max.z -+ // Leaf end - Optimize AABB - ); - } - diff --git a/leaf-server/minecraft-patches/features/0129-Make-removeIf-slightly-faster.patch b/leaf-server/minecraft-patches/features/0128-Make-removeIf-slightly-faster.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0129-Make-removeIf-slightly-faster.patch rename to leaf-server/minecraft-patches/features/0128-Make-removeIf-slightly-faster.patch diff --git a/leaf-server/minecraft-patches/features/0130-Optimize-LinearPalette.patch b/leaf-server/minecraft-patches/features/0129-Optimize-LinearPalette.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0130-Optimize-LinearPalette.patch rename to leaf-server/minecraft-patches/features/0129-Optimize-LinearPalette.patch diff --git a/leaf-server/minecraft-patches/features/0131-Slightly-optimized-VarInt-write.patch b/leaf-server/minecraft-patches/features/0130-Slightly-optimized-VarInt-write.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0131-Slightly-optimized-VarInt-write.patch rename to leaf-server/minecraft-patches/features/0130-Slightly-optimized-VarInt-write.patch diff --git a/leaf-server/minecraft-patches/features/0132-Rewrite-ClientboundLightUpdatePacketData.patch b/leaf-server/minecraft-patches/features/0131-Rewrite-ClientboundLightUpdatePacketData.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0132-Rewrite-ClientboundLightUpdatePacketData.patch rename to leaf-server/minecraft-patches/features/0131-Rewrite-ClientboundLightUpdatePacketData.patch diff --git a/leaf-server/minecraft-patches/features/0136-Async-chunk-sending.patch b/leaf-server/minecraft-patches/features/0132-Async-chunk-sending.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0136-Async-chunk-sending.patch rename to leaf-server/minecraft-patches/features/0132-Async-chunk-sending.patch diff --git a/leaf-server/minecraft-patches/features/0133-Some-Optimizations-on-SerializableChunkData.patch b/leaf-server/minecraft-patches/features/0133-Some-Optimizations-on-SerializableChunkData.patch deleted file mode 100644 index 126a1d8c..00000000 --- a/leaf-server/minecraft-patches/features/0133-Some-Optimizations-on-SerializableChunkData.patch +++ /dev/null @@ -1,84 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Tue, 25 Feb 2025 21:13:54 +0100 -Subject: [PATCH] Some Optimizations on SerializableChunkData - - -diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java -index 6b6aaeca14178b5b709e20ae13552d42217f15c0..c0939c311c554a4660b80725294663bab7915733 100644 ---- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java -+++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java -@@ -502,14 +502,16 @@ public record SerializableChunkData( - throw new IllegalArgumentException("Chunk can't be serialized: " + chunk); - } else { - ChunkPos pos = chunk.getPos(); -- List list = new ArrayList<>(); final List sectionsList = list; // Paper - starlight - OBFHELPER -- LevelChunkSection[] sections = chunk.getSections(); -- LevelLightEngine lightEngine = level.getChunkSource().getLightEngine(); - - // Paper start - starlight - final int minLightSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinLightSection(level); - final int maxLightSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxLightSection(level); - final int minBlockSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(level); -+ // Leaf start - Some Optimizations on SerializableChunkData -+ // Pre-allocate with correct capacity to avoid resizing -+ final int expectedSectionCount = maxLightSection - minLightSection + 1; -+ List list = new ArrayList<>(expectedSectionCount); -+ // Leaf end - Some Optimizations on SerializableChunkData - - final LevelChunkSection[] chunkSections = chunk.getSections(); - final ca.spottedleaf.moonrise.patches.starlight.light.SWMRNibbleArray[] blockNibbles = ((ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk)chunk).starlight$getBlockNibbles(); -@@ -541,10 +543,11 @@ public record SerializableChunkData( - ((ca.spottedleaf.moonrise.patches.starlight.storage.StarlightSectionData)(Object)sectionData).starlight$setSkyLightState(skyNibble.state); - } - -- sectionsList.add(sectionData); -+ list.add(sectionData); // Leaf - Some Optimizations on SerializableChunkData - } - // Paper end - starlight - -+ // Pre-allocate block entities list with exact size needed - List list1 = new ArrayList<>(chunk.getBlockEntitiesPos().size()); - - for (BlockPos blockPos : chunk.getBlockEntitiesPos()) { -@@ -554,7 +557,16 @@ public record SerializableChunkData( - } - } - -- List list2 = new ArrayList<>(); -+ // Leaf start - Some Optimizations on SerializableChunkData -+ // For entities, use an initial estimated capacity if it's a ProtoChunk -+ int entityEstimate = 64; // Reasonable default size -+ if (chunk.getPersistedStatus().getChunkType() == ChunkType.PROTOCHUNK) { -+ ProtoChunk protoChunk = (ProtoChunk)chunk; -+ entityEstimate = Math.max(16, protoChunk.getEntities().size()); -+ } -+ List list2 = new ArrayList<>(entityEstimate); -+ // Leaf end - Some Optimizations on SerializableChunkData -+ - long[] longs = null; - if (chunk.getPersistedStatus().getChunkType() == ChunkType.PROTOCHUNK) { - ProtoChunk protoChunk = (ProtoChunk)chunk; -@@ -570,14 +582,18 @@ public record SerializableChunkData( - for (Entry entry : chunk.getHeightmaps()) { - if (chunk.getPersistedStatus().heightmapsAfter().contains(entry.getKey())) { - long[] rawData = entry.getValue().getRawData(); -- map.put(entry.getKey(), (long[])rawData.clone()); -+ map.put(entry.getKey(), Arrays.copyOf(rawData, rawData.length)); // Leaf - Some Optimizations on SerializableChunkData - } - } - - ChunkAccess.PackedTicks ticksForSerialization = chunk.getTicksForSerialization(level.getGameTime()); -- ShortList[] lists = Arrays.stream(chunk.getPostProcessing()) -- .map(list3 -> list3 != null ? new ShortArrayList(list3) : null) -- .toArray(ShortList[]::new); -+ // Leaf start - Some Optimizations on SerializableChunkData -+ ShortList[] postProcessing = chunk.getPostProcessing(); -+ ShortList[] lists = new ShortList[postProcessing.length]; -+ for (int i = 0; i < postProcessing.length; i++) { -+ lists[i] = postProcessing[i] != null ? new ShortArrayList(postProcessing[i]) : null; -+ } -+ // Leaf end - Some Optimizations on SerializableChunkData - CompoundTag compoundTag = packStructureData( - StructurePieceSerializationContext.fromLevel(level), pos, chunk.getAllStarts(), chunk.getAllReferences() - ); diff --git a/leaf-server/minecraft-patches/features/0137-Spawner-Configurations.patch b/leaf-server/minecraft-patches/features/0133-Spawner-Configurations.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0137-Spawner-Configurations.patch rename to leaf-server/minecraft-patches/features/0133-Spawner-Configurations.patch diff --git a/leaf-server/minecraft-patches/features/0134-Rework-ChunkHolderManager.patch b/leaf-server/minecraft-patches/features/0134-Rework-ChunkHolderManager.patch deleted file mode 100644 index a95feb8d..00000000 --- a/leaf-server/minecraft-patches/features/0134-Rework-ChunkHolderManager.patch +++ /dev/null @@ -1,134 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Thu, 27 Feb 2025 23:39:32 +0100 -Subject: [PATCH] Rework ChunkHolderManager - - -diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -index be820c6093dd2ae7642b9bee11edf65e3a8d7242..d6a30d6735d24f24a8108b6a5d15725587bb662a 100644 ---- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -+++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -@@ -736,24 +736,20 @@ public final class ChunkHolderManager { - - final int sectionShift = ((ChunkSystemServerLevel)this.world).moonrise$getRegionChunkShift(); - -- final Predicate> expireNow = (final Ticket ticket) -> { -- long removeDelay = ((ChunkSystemTicket)(Object)ticket).moonrise$getRemoveDelay(); -- if (removeDelay == NO_TIMEOUT_MARKER) { -- return false; -- } -- --removeDelay; -- ((ChunkSystemTicket)(Object)ticket).moonrise$setRemoveDelay(removeDelay); -- return removeDelay <= 0L; -- }; -- -+ // Leaf start - Rework ChunkHolderManager -+ // Collect sections to process first to avoid concurrent modification issues -+ List sectionKeys = new ArrayList<>(); - for (final PrimitiveIterator.OfLong iterator = this.sectionToChunkToExpireCount.keyIterator(); iterator.hasNext();) { -- final long sectionKey = iterator.nextLong(); -+ sectionKeys.add(iterator.nextLong()); -+ } - -+ for (final Long sectionKey : sectionKeys) { -+ // Skip if section was removed concurrently - if (!this.sectionToChunkToExpireCount.containsKey(sectionKey)) { -- // removed concurrently - continue; - } - -+ // Acquire lock for this section only - final ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock( - CoordinateUtils.getChunkX(sectionKey) << sectionShift, - CoordinateUtils.getChunkZ(sectionKey) << sectionShift -@@ -761,11 +757,15 @@ public final class ChunkHolderManager { - - try { - final Long2IntOpenHashMap chunkToExpireCount = this.sectionToChunkToExpireCount.get(sectionKey); -- if (chunkToExpireCount == null) { -- // lost to some race -+ if (chunkToExpireCount == null || chunkToExpireCount.isEmpty()) { -+ // Section was removed or is empty, clean up -+ if (chunkToExpireCount != null && chunkToExpireCount.isEmpty()) { -+ this.sectionToChunkToExpireCount.remove(sectionKey); -+ } - continue; - } - -+ // Process each chunk in this section - for (final Iterator iterator1 = chunkToExpireCount.long2IntEntrySet().fastIterator(); iterator1.hasNext();) { - final Long2IntMap.Entry entry = iterator1.next(); - -@@ -773,33 +773,51 @@ public final class ChunkHolderManager { - final int expireCount = entry.getIntValue(); - - final SortedArraySet> tickets = this.tickets.get(chunkKey); -+ if (tickets == null || tickets.isEmpty()) { -+ iterator1.remove(); -+ continue; -+ } -+ - final int levelBefore = getTicketLevelAt(tickets); -+ int expiredCount = 0; - -- final int sizeBefore = tickets.size(); -- tickets.removeIf(expireNow); -- final int sizeAfter = tickets.size(); -- final int levelAfter = getTicketLevelAt(tickets); -+ // More efficient ticket processing - avoids creating a new predicate each time -+ for (Iterator> ticketIterator = tickets.iterator(); ticketIterator.hasNext();) { -+ Ticket ticket = ticketIterator.next(); -+ long removeDelay = ((ChunkSystemTicket)(Object)ticket).moonrise$getRemoveDelay(); -+ -+ if (removeDelay == NO_TIMEOUT_MARKER) { -+ continue; -+ } -+ -+ --removeDelay; -+ if (removeDelay <= 0) { -+ ticketIterator.remove(); -+ expiredCount++; -+ } else { -+ ((ChunkSystemTicket)(Object)ticket).moonrise$setRemoveDelay(removeDelay); -+ } -+ } - - if (tickets.isEmpty()) { - this.tickets.remove(chunkKey); - } -+ -+ final int levelAfter = getTicketLevelAt(tickets); - if (levelBefore != levelAfter) { - this.updateTicketLevel(chunkKey, levelAfter); - } - -- final int newExpireCount = expireCount - (sizeBefore - sizeAfter); -- -- if (newExpireCount == expireCount) { -- continue; -- } -- -- if (newExpireCount != 0) { -- entry.setValue(newExpireCount); -- } else { -+ // Update expire count -+ final int newExpireCount = expireCount - expiredCount; -+ if (newExpireCount <= 0) { - iterator1.remove(); -+ } else if (newExpireCount != expireCount) { -+ entry.setValue(newExpireCount); - } - } - -+ // Remove empty sections - if (chunkToExpireCount.isEmpty()) { - this.sectionToChunkToExpireCount.remove(sectionKey); - } -@@ -807,6 +825,7 @@ public final class ChunkHolderManager { - this.ticketLockArea.unlock(ticketLock); - } - } -+ // Leaf end - Rework ChunkHolderManager - - this.processTicketUpdates(); - } diff --git a/leaf-server/minecraft-patches/features/0138-SparklyPaper-Parallel-world-ticking.patch b/leaf-server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch similarity index 99% rename from leaf-server/minecraft-patches/features/0138-SparklyPaper-Parallel-world-ticking.patch rename to leaf-server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch index b8cc4514..25ad7ee0 100644 --- a/leaf-server/minecraft-patches/features/0138-SparklyPaper-Parallel-world-ticking.patch +++ b/leaf-server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch @@ -6,10 +6,10 @@ Subject: [PATCH] SparklyPaper: Parallel world ticking Original project: https://github.com/SparklyPower/SparklyPaper diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -index d6a30d6735d24f24a8108b6a5d15725587bb662a..39517966935265bc4533d4ce414d2df72df5a614 100644 +index be820c6093dd2ae7642b9bee11edf65e3a8d7242..06ac3537f5655d048d770bb004243f207fad9faa 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -@@ -1050,7 +1050,7 @@ public final class ChunkHolderManager { +@@ -1031,7 +1031,7 @@ public final class ChunkHolderManager { if (changedFullStatus.isEmpty()) { return; } @@ -18,7 +18,7 @@ index d6a30d6735d24f24a8108b6a5d15725587bb662a..39517966935265bc4533d4ce414d2df7 this.taskScheduler.scheduleChunkTask(() -> { final ArrayDeque pendingFullLoadUpdate = ChunkHolderManager.this.pendingFullLoadUpdate; for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { -@@ -1076,7 +1076,12 @@ public final class ChunkHolderManager { +@@ -1057,7 +1057,12 @@ public final class ChunkHolderManager { // note: never call while inside the chunk system, this will absolutely break everything public void processUnloads() { @@ -32,7 +32,7 @@ index d6a30d6735d24f24a8108b6a5d15725587bb662a..39517966935265bc4533d4ce414d2df7 if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { throw new IllegalStateException("Cannot unload chunks recursively"); -@@ -1358,7 +1363,7 @@ public final class ChunkHolderManager { +@@ -1339,7 +1344,7 @@ public final class ChunkHolderManager { List changedFullStatus = null; diff --git a/leaf-server/minecraft-patches/features/0139-SparklyPaper-Track-each-world-MSPT.patch b/leaf-server/minecraft-patches/features/0135-SparklyPaper-Track-each-world-MSPT.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0139-SparklyPaper-Track-each-world-MSPT.patch rename to leaf-server/minecraft-patches/features/0135-SparklyPaper-Track-each-world-MSPT.patch diff --git a/leaf-server/minecraft-patches/features/0140-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch b/leaf-server/minecraft-patches/features/0136-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0140-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch rename to leaf-server/minecraft-patches/features/0136-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch diff --git a/leaf-server/minecraft-patches/features/0141-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch b/leaf-server/minecraft-patches/features/0137-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0141-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch rename to leaf-server/minecraft-patches/features/0137-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch diff --git a/leaf-server/minecraft-patches/features/0142-Remove-streams-on-InsideBrownianWalk.patch b/leaf-server/minecraft-patches/features/0138-Remove-streams-on-InsideBrownianWalk.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0142-Remove-streams-on-InsideBrownianWalk.patch rename to leaf-server/minecraft-patches/features/0138-Remove-streams-on-InsideBrownianWalk.patch diff --git a/leaf-server/minecraft-patches/features/0143-Use-BFS-on-getSlopeDistance.patch b/leaf-server/minecraft-patches/features/0139-Use-BFS-on-getSlopeDistance.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0143-Use-BFS-on-getSlopeDistance.patch rename to leaf-server/minecraft-patches/features/0139-Use-BFS-on-getSlopeDistance.patch diff --git a/leaf-server/minecraft-patches/features/0144-Paper-PR-Throttle-failed-spawn-attempts.patch b/leaf-server/minecraft-patches/features/0140-Paper-PR-Throttle-failed-spawn-attempts.patch similarity index 98% rename from leaf-server/minecraft-patches/features/0144-Paper-PR-Throttle-failed-spawn-attempts.patch rename to leaf-server/minecraft-patches/features/0140-Paper-PR-Throttle-failed-spawn-attempts.patch index 94e2d7b8..169e432c 100644 --- a/leaf-server/minecraft-patches/features/0144-Paper-PR-Throttle-failed-spawn-attempts.patch +++ b/leaf-server/minecraft-patches/features/0140-Paper-PR-Throttle-failed-spawn-attempts.patch @@ -174,7 +174,7 @@ index 3a6db5bc0c8be7d68e15317a621c1965fdc3a9bd..50a9903367f49ece2a267d10944b1515 // 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 c0939c311c554a4660b80725294663bab7915733..e2df93b2500a74c4cecac1515f3991967a07a052 100644 +index 6b6aaeca14178b5b709e20ae13552d42217f15c0..e9ece9b618b0a9eb82b9f07a09ee6cb60cf7ec16 100644 --- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java @@ -92,6 +92,7 @@ public record SerializableChunkData( @@ -229,7 +229,7 @@ index c0939c311c554a4660b80725294663bab7915733..e2df93b2500a74c4cecac1515f399196 if (chunkType == ChunkType.LEVELCHUNK) { return this.loadStarlightLightData(level, new ImposterProtoChunk((LevelChunk)chunkAccess, false)); // Paper - starlight } else { -@@ -603,6 +627,7 @@ public record SerializableChunkData( +@@ -587,6 +611,7 @@ public record SerializableChunkData( persistentDataContainer = chunk.persistentDataContainer.toTagCompound(); } // CraftBukkit end @@ -237,7 +237,7 @@ index c0939c311c554a4660b80725294663bab7915733..e2df93b2500a74c4cecac1515f399196 return new SerializableChunkData( level.registryAccess().lookupOrThrow(Registries.BIOME), pos, -@@ -623,6 +648,7 @@ public record SerializableChunkData( +@@ -607,6 +632,7 @@ public record SerializableChunkData( list1, compoundTag , persistentDataContainer // CraftBukkit - persistentDataContainer @@ -245,7 +245,7 @@ index c0939c311c554a4660b80725294663bab7915733..e2df93b2500a74c4cecac1515f399196 ); } } -@@ -719,6 +745,21 @@ public record SerializableChunkData( +@@ -703,6 +729,21 @@ public record SerializableChunkData( compoundTag.put("ChunkBukkitValues", this.persistentDataContainer); } // CraftBukkit end @@ -267,7 +267,7 @@ index c0939c311c554a4660b80725294663bab7915733..e2df93b2500a74c4cecac1515f399196 // 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 -@@ -947,4 +988,50 @@ public record SerializableChunkData( +@@ -931,4 +972,50 @@ public record SerializableChunkData( } // Paper end - starlight - convert from record } diff --git a/leaf-server/minecraft-patches/features/0145-Improve-BlockEntity-ticking-isRemoved-check.patch b/leaf-server/minecraft-patches/features/0141-Improve-BlockEntity-ticking-isRemoved-check.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0145-Improve-BlockEntity-ticking-isRemoved-check.patch rename to leaf-server/minecraft-patches/features/0141-Improve-BlockEntity-ticking-isRemoved-check.patch diff --git a/leaf-server/minecraft-patches/features/0146-Raytrace-AntiXray-SDK-integration.patch b/leaf-server/minecraft-patches/features/0142-Raytrace-AntiXray-SDK-integration.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0146-Raytrace-AntiXray-SDK-integration.patch rename to leaf-server/minecraft-patches/features/0142-Raytrace-AntiXray-SDK-integration.patch diff --git a/leaf-server/minecraft-patches/features/0147-Optimize-addOrUpdateTransientModifier.patch b/leaf-server/minecraft-patches/features/0143-Optimize-addOrUpdateTransientModifier.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0147-Optimize-addOrUpdateTransientModifier.patch rename to leaf-server/minecraft-patches/features/0143-Optimize-addOrUpdateTransientModifier.patch diff --git a/leaf-server/minecraft-patches/features/0148-Optimize-ContextMap.create.patch b/leaf-server/minecraft-patches/features/0144-Optimize-ContextMap.create.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0148-Optimize-ContextMap.create.patch rename to leaf-server/minecraft-patches/features/0144-Optimize-ContextMap.create.patch diff --git a/leaf-server/minecraft-patches/features/0149-Micro-optimizations-for-random-tick.patch b/leaf-server/minecraft-patches/features/0145-Micro-optimizations-for-random-tick.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0149-Micro-optimizations-for-random-tick.patch rename to leaf-server/minecraft-patches/features/0145-Micro-optimizations-for-random-tick.patch diff --git a/leaf-server/minecraft-patches/features/0150-Remove-streams-on-updateConnectedPlayersWithinRange.patch b/leaf-server/minecraft-patches/features/0146-Remove-streams-on-updateConnectedPlayersWithinRange.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0150-Remove-streams-on-updateConnectedPlayersWithinRange.patch rename to leaf-server/minecraft-patches/features/0146-Remove-streams-on-updateConnectedPlayersWithinRange.patch diff --git a/leaf-server/minecraft-patches/features/0151-Remove-streams-on-PlayerDetector.patch b/leaf-server/minecraft-patches/features/0147-Remove-streams-on-PlayerDetector.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0151-Remove-streams-on-PlayerDetector.patch rename to leaf-server/minecraft-patches/features/0147-Remove-streams-on-PlayerDetector.patch diff --git a/leaf-server/minecraft-patches/features/0152-Async-Block-Finding.patch b/leaf-server/minecraft-patches/features/0148-Async-Block-Finding.patch similarity index 96% rename from leaf-server/minecraft-patches/features/0152-Async-Block-Finding.patch rename to leaf-server/minecraft-patches/features/0148-Async-Block-Finding.patch index ca8e3fac..c527e135 100644 --- a/leaf-server/minecraft-patches/features/0152-Async-Block-Finding.patch +++ b/leaf-server/minecraft-patches/features/0148-Async-Block-Finding.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Async Block Finding diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..007da9cb39ff76285c52ce0abdff60997acdff0f 100644 +index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..d70ed3ace6fa8f97bcc0d493842f44f43072a610 100644 --- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java @@ -20,6 +20,18 @@ public abstract class MoveToBlockGoal extends Goal { @@ -102,7 +102,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..007da9cb39ff76285c52ce0abdff6099 + this.isValidTarget(this.mob.level(), pos)) { + + this.blockPos = pos; -+ this.mob.movingTarget = pos == BlockPos.ZERO ? null : pos; ++ this.mob.movingTarget = this.blockPos == BlockPos.ZERO ? null : this.blockPos; // Use the assigned blockPos + return true; + } + } @@ -177,7 +177,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..007da9cb39ff76285c52ce0abdff6099 - this.blockPos = mutableBlockPos; - this.mob.movingTarget = mutableBlockPos == BlockPos.ZERO ? null : mutableBlockPos.immutable(); // Paper + this.blockPos = mutableBlockPos.immutable(); // Leaf - Async Block Finding -+ this.mob.movingTarget = this.blockPos == BlockPos.ZERO ? null : this.blockPos; // Paper // Leaf - Async Block Finding ++ this.mob.movingTarget = this.blockPos == BlockPos.ZERO ? null : this.blockPos; // Paper // Leaf - Async Block Finding - Use the assigned blockPos return true; } } diff --git a/leaf-server/minecraft-patches/features/0153-Use-direct-iteration-on-Sensing.tick.patch b/leaf-server/minecraft-patches/features/0149-Use-direct-iteration-on-Sensing.tick.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0153-Use-direct-iteration-on-Sensing.tick.patch rename to leaf-server/minecraft-patches/features/0149-Use-direct-iteration-on-Sensing.tick.patch diff --git a/leaf-server/minecraft-patches/features/0154-Optimise-non-flush-packet-sending.patch b/leaf-server/minecraft-patches/features/0150-Optimise-non-flush-packet-sending.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0154-Optimise-non-flush-packet-sending.patch rename to leaf-server/minecraft-patches/features/0150-Optimise-non-flush-packet-sending.patch diff --git a/leaf-server/minecraft-patches/features/0155-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch b/leaf-server/minecraft-patches/features/0151-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0155-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch rename to leaf-server/minecraft-patches/features/0151-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch diff --git a/leaf-server/minecraft-patches/features/0157-Null-handling-on-MultifaceSpreader.patch b/leaf-server/minecraft-patches/features/0152-Null-handling-on-MultifaceSpreader.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0157-Null-handling-on-MultifaceSpreader.patch rename to leaf-server/minecraft-patches/features/0152-Null-handling-on-MultifaceSpreader.patch diff --git a/leaf-server/minecraft-patches/features/0158-More-virtual-threads.patch b/leaf-server/minecraft-patches/features/0153-More-virtual-threads.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0158-More-virtual-threads.patch rename to leaf-server/minecraft-patches/features/0153-More-virtual-threads.patch diff --git a/leaf-server/minecraft-patches/features/0154-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch b/leaf-server/minecraft-patches/features/0154-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch new file mode 100644 index 00000000..11c5606a --- /dev/null +++ b/leaf-server/minecraft-patches/features/0154-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch @@ -0,0 +1,148 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sun, 13 Apr 2025 16:15:17 +0200 +Subject: [PATCH] Replace ConcurrentLong2ReferenceChainedHashTable with custom + map + + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java b/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java +index 7eafc5b7cba23d8dec92ecc1050afe3fd8c9e309..c2d5e83f0bdf98d3c07d6da2bba3b1ebaf7307d5 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java +@@ -7,6 +7,8 @@ import com.google.gson.JsonElement; + import com.google.gson.JsonObject; + import it.unimi.dsi.fastutil.longs.LongIterator; + import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; ++import org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable; ++ + import java.util.ArrayList; + import java.util.Iterator; + import java.util.List; +@@ -16,7 +18,7 @@ public final class ChunkUnloadQueue { + + public final int coordinateShift; + private final AtomicLong orderGenerator = new AtomicLong(); +- private final ConcurrentLong2ReferenceChainedHashTable unloadSections = new ConcurrentLong2ReferenceChainedHashTable<>(); ++ private final LeafConcurrentLong2ReferenceChainedHashTable unloadSections = new LeafConcurrentLong2ReferenceChainedHashTable<>(); + + /* + * Note: write operations do not occur in parallel for any given section. +@@ -32,8 +34,8 @@ public final class ChunkUnloadQueue { + public List retrieveForAllRegions() { + final List ret = new ArrayList<>(); + +- for (final Iterator> iterator = this.unloadSections.entryIterator(); iterator.hasNext();) { +- final ConcurrentLong2ReferenceChainedHashTable.TableEntry entry = iterator.next(); ++ for (final Iterator> iterator = this.unloadSections.entryIterator(); iterator.hasNext();) { ++ final LeafConcurrentLong2ReferenceChainedHashTable.TableEntry entry = iterator.next(); + final long key = entry.getKey(); + final UnloadSection section = entry.getValue(); + final int sectionX = CoordinateUtils.getChunkX(key); +@@ -141,4 +143,4 @@ public final class ChunkUnloadQueue { + this.order = order; + } + } +-} +\ No newline at end of file ++} +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +index 06ac3537f5655d048d770bb004243f207fad9faa..a1f328a5c4ccc030c99762a68008ab1ecebdc06e 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +@@ -40,6 +40,7 @@ import net.minecraft.util.SortedArraySet; + import net.minecraft.util.Unit; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.chunk.LevelChunk; ++import org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable; + import org.slf4j.Logger; + import java.io.IOException; + import java.text.DecimalFormat; +@@ -71,11 +72,11 @@ public final class ChunkHolderManager { + private static final long PROBE_MARKER = Long.MIN_VALUE + 1; + public final ReentrantAreaLock ticketLockArea; + +- private final ConcurrentLong2ReferenceChainedHashTable>> tickets = new ConcurrentLong2ReferenceChainedHashTable<>(); +- private final ConcurrentLong2ReferenceChainedHashTable sectionToChunkToExpireCount = new ConcurrentLong2ReferenceChainedHashTable<>(); ++ private final LeafConcurrentLong2ReferenceChainedHashTable>> tickets = new LeafConcurrentLong2ReferenceChainedHashTable<>(); ++ private final LeafConcurrentLong2ReferenceChainedHashTable sectionToChunkToExpireCount = new LeafConcurrentLong2ReferenceChainedHashTable<>(); + final ChunkUnloadQueue unloadQueue; + +- private final ConcurrentLong2ReferenceChainedHashTable chunkHolders = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(16384, 0.25f); ++ private final LeafConcurrentLong2ReferenceChainedHashTable chunkHolders = LeafConcurrentLong2ReferenceChainedHashTable.createWithCapacity(16384, 0.25f); + private final ServerLevel world; + private final ChunkTaskScheduler taskScheduler; + private long currentTick; +@@ -1422,9 +1423,9 @@ public final class ChunkHolderManager { + final JsonArray allTicketsJson = new JsonArray(); + ret.add("tickets", allTicketsJson); + +- for (final Iterator>>> iterator = this.tickets.entryIterator(); ++ for (final Iterator>>> iterator = this.tickets.entryIterator(); + iterator.hasNext();) { +- final ConcurrentLong2ReferenceChainedHashTable.TableEntry>> coordinateTickets = iterator.next(); ++ final LeafConcurrentLong2ReferenceChainedHashTable.TableEntry>> coordinateTickets = iterator.next(); + final long coordinate = coordinateTickets.getKey(); + final SortedArraySet> tickets = coordinateTickets.getValue(); + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java +index 310a8f80debadd64c2d962ebf83b7d0505ce6e42..b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java +@@ -10,6 +10,8 @@ import it.unimi.dsi.fastutil.longs.Long2ByteLinkedOpenHashMap; + import it.unimi.dsi.fastutil.shorts.Short2ByteLinkedOpenHashMap; + import it.unimi.dsi.fastutil.shorts.Short2ByteMap; + import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; ++import org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable; ++ + import java.lang.invoke.VarHandle; + import java.util.ArrayDeque; + import java.util.ArrayList; +@@ -35,11 +37,11 @@ public abstract class ThreadedTicketLevelPropagator { + } + + private final UpdateQueue updateQueue; +- private final ConcurrentLong2ReferenceChainedHashTable
sections; ++ private final LeafConcurrentLong2ReferenceChainedHashTable
sections; + + public ThreadedTicketLevelPropagator() { + this.updateQueue = new UpdateQueue(); +- this.sections = new ConcurrentLong2ReferenceChainedHashTable<>(); ++ this.sections = new LeafConcurrentLong2ReferenceChainedHashTable<>(); + } + + // must hold ticket lock for: +diff --git a/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java b/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java +index 1487b7d8be435b3fbad2aabd05796965b4775a87..54c425ba84c7c70becdfbde08812afdde777f5a8 100644 +--- a/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java ++++ b/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java +@@ -27,6 +27,8 @@ import net.minecraft.world.level.chunk.LightChunkGetter; + import net.minecraft.world.level.chunk.status.ChunkStatus; + import net.minecraft.world.level.lighting.LayerLightEventListener; + import net.minecraft.world.level.lighting.LevelLightEngine; ++import org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable; ++ + import java.util.ArrayDeque; + import java.util.ArrayList; + import java.util.HashSet; +@@ -740,7 +742,7 @@ public final class StarLightInterface { + + public static final class ServerLightQueue extends LightQueue { + +- private final ConcurrentLong2ReferenceChainedHashTable chunkTasks = new ConcurrentLong2ReferenceChainedHashTable<>(); ++ private final LeafConcurrentLong2ReferenceChainedHashTable chunkTasks = new LeafConcurrentLong2ReferenceChainedHashTable<>(); + + public ServerLightQueue(final StarLightInterface lightInterface) { + super(lightInterface); +diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java +index b1f1b596a597d559aa672a3cb46a03917ad746af..d75f85208da0c7424fc95ae0d8ebb0a725dda0a7 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -72,7 +72,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + @VisibleForDebug + private NaturalSpawner.SpawnState lastSpawnState; + // Paper start +- private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>(); ++ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable fullChunks = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>(); + public int getFullChunksCount() { + return this.fullChunks.size(); + } diff --git a/leaf-server/minecraft-patches/features/0156-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0155-Async-target-finding.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0156-Async-target-finding.patch rename to leaf-server/minecraft-patches/features/0155-Async-target-finding.patch diff --git a/leaf-server/minecraft-patches/features/0156-Reworked-ChunkHolderManager.patch b/leaf-server/minecraft-patches/features/0156-Reworked-ChunkHolderManager.patch new file mode 100644 index 00000000..a2e6aac8 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0156-Reworked-ChunkHolderManager.patch @@ -0,0 +1,151 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Mon, 14 Apr 2025 03:02:42 +0200 +Subject: [PATCH] Reworked ChunkHolderManager + + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +index a1f328a5c4ccc030c99762a68008ab1ecebdc06e..3de8d0fb485e55f3fc38a65c251f109335595468 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +@@ -349,12 +349,13 @@ public final class ChunkHolderManager { + @Override + protected void processLevelUpdates(final Long2ByteLinkedOpenHashMap updates) { + // first the necessary chunkholders must be created, so just update the ticket levels ++ final LeafConcurrentLong2ReferenceChainedHashTable holderMap = ChunkHolderManager.this.chunkHolders; + for (final Iterator iterator = updates.long2ByteEntrySet().fastIterator(); iterator.hasNext();) { + final Long2ByteMap.Entry entry = iterator.next(); + final long key = entry.getLongKey(); + final int newLevel = convertBetweenTicketLevels((int)entry.getByteValue()); + +- NewChunkHolder current = ChunkHolderManager.this.chunkHolders.get(key); ++ NewChunkHolder current = holderMap.get(key); + if (current == null && newLevel > MAX_TICKET_LEVEL) { + // not loaded and it shouldn't be loaded! + iterator.remove(); +@@ -371,7 +372,7 @@ public final class ChunkHolderManager { + if (current == null) { + // must create + current = ChunkHolderManager.this.createChunkHolder(key); +- ChunkHolderManager.this.chunkHolders.put(key, current); ++ holderMap.put(key, current); + current.updateTicketLevel(newLevel); + } else { + current.updateTicketLevel(newLevel); +@@ -737,20 +738,23 @@ public final class ChunkHolderManager { + + final int sectionShift = ((ChunkSystemServerLevel)this.world).moonrise$getRegionChunkShift(); + ++ + final Predicate> expireNow = (final Ticket ticket) -> { + long removeDelay = ((ChunkSystemTicket)(Object)ticket).moonrise$getRemoveDelay(); + if (removeDelay == NO_TIMEOUT_MARKER) { + return false; + } + --removeDelay; +- ((ChunkSystemTicket)(Object)ticket).moonrise$setRemoveDelay(removeDelay); +- return removeDelay <= 0L; ++ final long nextDelay = removeDelay - 1; ++ ((ChunkSystemTicket)(Object)ticket).moonrise$setRemoveDelay(nextDelay); ++ return nextDelay <= 0L; + }; + + for (final PrimitiveIterator.OfLong iterator = this.sectionToChunkToExpireCount.keyIterator(); iterator.hasNext();) { + final long sectionKey = iterator.nextLong(); + + if (!this.sectionToChunkToExpireCount.containsKey(sectionKey)) { ++ + // removed concurrently + continue; + } +@@ -773,37 +777,62 @@ public final class ChunkHolderManager { + final long chunkKey = entry.getLongKey(); + final int expireCount = entry.getIntValue(); + ++ + final SortedArraySet> tickets = this.tickets.get(chunkKey); +- final int levelBefore = getTicketLevelAt(tickets); ++ if (tickets == null) { ++ iterator1.remove(); ++ continue; ++ } + ++ final int levelBefore; ++ final Ticket firstBefore; + final int sizeBefore = tickets.size(); +- tickets.removeIf(expireNow); +- final int sizeAfter = tickets.size(); +- final int levelAfter = getTicketLevelAt(tickets); + +- if (tickets.isEmpty()) { +- this.tickets.remove(chunkKey); +- } +- if (levelBefore != levelAfter) { +- this.updateTicketLevel(chunkKey, levelAfter); ++ if (!tickets.isEmpty()) { ++ firstBefore = tickets.first(); ++ levelBefore = firstBefore.getTicketLevel(); ++ } else { ++ firstBefore = null; ++ levelBefore = MAX_TICKET_LEVEL + 1; + } + +- final int newExpireCount = expireCount - (sizeBefore - sizeAfter); ++ final boolean changed = tickets.removeIf(expireNow); + +- if (newExpireCount == expireCount) { +- continue; +- } ++ if (changed) { ++ final int sizeAfter = tickets.size(); ++ final int levelAfter; ++ boolean levelMightHaveChanged = true; + +- if (newExpireCount != 0) { +- entry.setValue(newExpireCount); +- } else { +- iterator1.remove(); ++ if (tickets.isEmpty()) { ++ levelAfter = MAX_TICKET_LEVEL + 1; ++ this.tickets.remove(chunkKey); ++ } else { ++ final Ticket firstAfter = tickets.first(); ++ if (firstBefore == firstAfter) { ++ levelMightHaveChanged = false; ++ levelAfter = levelBefore; ++ } else { ++ levelAfter = firstAfter.getTicketLevel(); ++ } ++ } ++ ++ if (levelMightHaveChanged && levelBefore != levelAfter) { ++ this.updateTicketLevel(chunkKey, levelAfter); ++ } ++ ++ final int removedCount = sizeBefore - sizeAfter; ++ if (removedCount > 0) { ++ final int newExpireCount = expireCount - removedCount; ++ if (newExpireCount > 0) { ++ entry.setValue(newExpireCount); ++ } else { ++ iterator1.remove(); ++ } ++ } + } +- } + +- if (chunkToExpireCount.isEmpty()) { +- this.sectionToChunkToExpireCount.remove(sectionKey); + } ++ if (chunkToExpireCount.isEmpty()) { this.sectionToChunkToExpireCount.remove(sectionKey); } + } finally { + this.ticketLockArea.unlock(ticketLock); + } +@@ -812,6 +841,7 @@ public final class ChunkHolderManager { + this.processTicketUpdates(); + } + ++ + public NewChunkHolder getChunkHolder(final int chunkX, final int chunkZ) { + return this.chunkHolders.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); + } diff --git a/leaf-server/minecraft-patches/features/0157-Optimize-ThreadedTicketLevelPropagator.patch b/leaf-server/minecraft-patches/features/0157-Optimize-ThreadedTicketLevelPropagator.patch new file mode 100644 index 00000000..cc977ac4 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0157-Optimize-ThreadedTicketLevelPropagator.patch @@ -0,0 +1,277 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Mon, 14 Apr 2025 14:36:57 +0200 +Subject: [PATCH] Optimize ThreadedTicketLevelPropagator + + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java +index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc1de204b7 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java +@@ -780,11 +780,13 @@ public abstract class ThreadedTicketLevelPropagator { + // minimum number of bits to represent [0, SECTION_SIZE * SECTION_CACHE_WIDTH) + private static final int COORDINATE_BITS = 9; + private static final int COORDINATE_SIZE = 1 << COORDINATE_BITS; ++ + static { + if ((SECTION_SIZE * SECTION_CACHE_WIDTH) > (1 << COORDINATE_BITS)) { + throw new IllegalStateException("Adjust COORDINATE_BITS"); + } + } ++ + // index = x + (z * SECTION_CACHE_WIDTH) + // (this requires x >= 0 and z >= 0) + private final Section[] sections = new Section[SECTION_CACHE_WIDTH * SECTION_CACHE_WIDTH]; +@@ -828,8 +830,8 @@ public abstract class ThreadedTicketLevelPropagator { + // must hold ticket lock for (centerSectionX,centerSectionZ) in radius rad + // must call setupEncodeOffset + private final void setupCaches(final ThreadedTicketLevelPropagator propagator, +- final int centerSectionX, final int centerSectionZ, +- final int rad) { ++ final int centerSectionX, final int centerSectionZ, ++ final int rad) { + for (int dz = -rad; dz <= rad; ++dz) { + for (int dx = -rad; dx <= rad; ++dx) { + final int sectionX = centerSectionX + dx; +@@ -847,29 +849,29 @@ public abstract class ThreadedTicketLevelPropagator { + } + + private final void setSectionInCache(final int sectionX, final int sectionZ, final Section section) { +- this.sections[sectionX + SECTION_CACHE_WIDTH*sectionZ + this.sectionIndexOffset] = section; ++ this.sections[sectionX + SECTION_CACHE_WIDTH * sectionZ + this.sectionIndexOffset] = section; + } + + private final Section getSection(final int sectionX, final int sectionZ) { +- return this.sections[sectionX + SECTION_CACHE_WIDTH*sectionZ + this.sectionIndexOffset]; ++ return this.sections[sectionX + SECTION_CACHE_WIDTH * sectionZ + this.sectionIndexOffset]; + } + + private final int getLevel(final int posX, final int posZ) { +- final Section section = this.sections[(posX >> SECTION_SHIFT) + SECTION_CACHE_WIDTH*(posZ >> SECTION_SHIFT) + this.sectionIndexOffset]; ++ final Section section = this.sections[(posX >> SECTION_SHIFT) + SECTION_CACHE_WIDTH * (posZ >> SECTION_SHIFT) + this.sectionIndexOffset]; + if (section != null) { +- return (int)section.levels[(posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT)] & 0xFF; ++ return (int) section.levels[(posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT)] & 0xFF; + } + + return 0; + } + + private final void setLevel(final int posX, final int posZ, final int to) { +- final Section section = this.sections[(posX >> SECTION_SHIFT) + SECTION_CACHE_WIDTH*(posZ >> SECTION_SHIFT) + this.sectionIndexOffset]; ++ final Section section = this.sections[(posX >> SECTION_SHIFT) + SECTION_CACHE_WIDTH * (posZ >> SECTION_SHIFT) + this.sectionIndexOffset]; + if (section != null) { + final int index = (posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); + final short level = section.levels[index]; +- section.levels[index] = (short)((level & ~0xFF) | (to & 0xFF)); +- this.updatedPositions.put(CoordinateUtils.getChunkKey(posX, posZ), (byte)to); ++ section.levels[index] = (short) ((level & ~0xFF) | (to & 0xFF)); ++ this.updatedPositions.put(CoordinateUtils.getChunkKey(posX, posZ), (byte) to); + } + } + +@@ -882,8 +884,8 @@ public abstract class ThreadedTicketLevelPropagator { + // next LEVEL_BITS (6) bits: propagated level [0, 63] + // propagation directions bitset (16 bits): + private static final long ALL_DIRECTIONS_BITSET = ( +- // z = -1 +- (1L << ((1 - 1) | ((1 - 1) << 2))) | ++ // z = -1 ++ (1L << ((1 - 1) | ((1 - 1) << 2))) | + (1L << ((1 + 0) | ((1 - 1) << 2))) | + (1L << ((1 + 1) | ((1 - 1) << 2))) | + +@@ -920,7 +922,7 @@ public abstract class ThreadedTicketLevelPropagator { + } + + private void ch(long bs, int shift) { +- int bitset = (int)(bs >>> shift); ++ int bitset = (int) (bs >>> shift); + for (int i = 0, len = Integer.bitCount(bitset); i < len; ++i) { + final int set = Integer.numberOfTrailingZeros(bitset); + final int tailingBit = (-bitset) & bitset; +@@ -1000,27 +1002,38 @@ public abstract class ThreadedTicketLevelPropagator { + final int decodeOffsetZ = -this.encodeOffsetZ; + final int encodeOffset = this.coordinateOffset; + final int sectionOffset = this.sectionIndexOffset; ++ final Section[] sectionsArray = this.sections; + + final Long2ByteLinkedOpenHashMap updatedPositions = this.updatedPositions; + + while (queueReadIndex < queueLength) { + final long queueValue = queue[queueReadIndex++]; + +- final int posX = ((int)queueValue & (COORDINATE_SIZE - 1)) + decodeOffsetX; +- final int posZ = (((int)queueValue >>> COORDINATE_BITS) & (COORDINATE_SIZE - 1)) + decodeOffsetZ; +- final int propagatedLevel = ((int)queueValue >>> (COORDINATE_BITS + COORDINATE_BITS)) & (LEVEL_COUNT - 1); ++ final int posX = ((int) queueValue & (COORDINATE_SIZE - 1)) + decodeOffsetX; ++ final int posZ = (((int) queueValue >>> COORDINATE_BITS) & (COORDINATE_SIZE - 1)) + decodeOffsetZ; ++ final int propagatedLevel = ((int) queueValue >>> (COORDINATE_BITS + COORDINATE_BITS)) & (LEVEL_COUNT - 1); + // note: the above code requires coordinate bits * 2 < 32 + // bitset is 16 bits +- int propagateDirectionBitset = (int)(queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); ++ int propagateDirectionBitset = (int) (queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); + + if ((queueValue & FLAG_RECHECK_LEVEL) != 0L) { +- if (this.getLevel(posX, posZ) != propagatedLevel) { ++ final int sectionX = posX >> SECTION_SHIFT; ++ final int sectionZ = posZ >> SECTION_SHIFT; ++ final Section section = sectionsArray[sectionX + (sectionZ * SECTION_CACHE_WIDTH) + sectionOffset]; ++ final int localIdx = (posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); ++ if ((section.levels[localIdx] & 0xFF) != propagatedLevel) { + // not at the level we expect, so something changed. + continue; + } + } else if ((queueValue & FLAG_WRITE_LEVEL) != 0L) { + // these are used to restore sources after a propagation decrease +- this.setLevel(posX, posZ, propagatedLevel); ++ final int sectionX = posX >> SECTION_SHIFT; ++ final int sectionZ = posZ >> SECTION_SHIFT; ++ final Section section = sectionsArray[sectionX + (sectionZ * SECTION_CACHE_WIDTH) + sectionOffset]; ++ final int localIdx = (posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); ++ final short currentLevel = section.levels[localIdx]; ++ section.levels[localIdx] = (short) ((currentLevel & ~0xFF) | (propagatedLevel & 0xFF)); ++ updatedPositions.put(CoordinateUtils.getChunkKey(posX, posZ), (byte) propagatedLevel); + } + + // this bitset represents the values that we have not propagated to +@@ -1036,8 +1049,8 @@ public abstract class ThreadedTicketLevelPropagator { + // must guarantee that either we propagate everything in 1 radius or we partially propagate for 1 radius + // but the rest not propagated are already handled + long currentPropagation = ~( +- // z = -1 +- (1L << ((2 - 1) | ((2 - 1) << 3))) | ++ // z = -1 ++ (1L << ((2 - 1) | ((2 - 1) << 3))) | + (1L << ((2 + 0) | ((2 - 1) << 3))) | + (1L << ((2 + 1) | ((2 - 1) << 3))) | + +@@ -1095,7 +1108,7 @@ public abstract class ThreadedTicketLevelPropagator { + currentPropagation ^= (bitsetLine1 | bitsetLine2 | bitsetLine3); + + // now try to propagate +- final Section section = this.sections[sectionIndex]; ++ final Section section = sectionsArray[sectionIndex]; + + // lower 8 bits are current level, next upper 7 bits are source level, next 1 bit is updated source flag + final short currentStoredLevel = section.levels[localIndex]; +@@ -1106,8 +1119,8 @@ public abstract class ThreadedTicketLevelPropagator { + } + + // update level +- section.levels[localIndex] = (short)((currentStoredLevel & ~0xFF) | (toPropagate & 0xFF)); +- updatedPositions.putAndMoveToLast(CoordinateUtils.getChunkKey(offX, offZ), (byte)toPropagate); ++ section.levels[localIndex] = (short) ((currentStoredLevel & ~0xFF) | (toPropagate & 0xFF)); ++ updatedPositions.putAndMoveToLast(CoordinateUtils.getChunkKey(offX, offZ), (byte) toPropagate); + + // queue next + if (toPropagate > 1) { +@@ -1115,7 +1128,7 @@ public abstract class ThreadedTicketLevelPropagator { + // the child bitset is 4x4, so we just shift each line by 4 + // add the propagation bitset offset to each line to make it easy to OR it into the propagation queue value + final long childPropagation = +- ((bitsetLine1 >>> (start)) << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = -1 ++ ((bitsetLine1 >>> (start)) << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = -1 + ((bitsetLine2 >>> (start + 8)) << (4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = 0 + ((bitsetLine3 >>> (start + (8 + 8))) << (4 + 4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); // z = 1 + +@@ -1125,7 +1138,7 @@ public abstract class ThreadedTicketLevelPropagator { + queue = this.resizeIncreaseQueue(); + } + queue[queueLength++] = +- ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | ++ ((long) (offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | + ((toPropagate & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | + childPropagation; //(ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); + continue; +@@ -1146,18 +1159,19 @@ public abstract class ThreadedTicketLevelPropagator { + final int decodeOffsetZ = -this.encodeOffsetZ; + final int encodeOffset = this.coordinateOffset; + final int sectionOffset = this.sectionIndexOffset; ++ final Section[] sectionsArray = this.sections; + + final Long2ByteLinkedOpenHashMap updatedPositions = this.updatedPositions; + + while (queueReadIndex < queueLength) { + final long queueValue = queue[queueReadIndex++]; + +- final int posX = ((int)queueValue & (COORDINATE_SIZE - 1)) + decodeOffsetX; +- final int posZ = (((int)queueValue >>> COORDINATE_BITS) & (COORDINATE_SIZE - 1)) + decodeOffsetZ; +- final int propagatedLevel = ((int)queueValue >>> (COORDINATE_BITS + COORDINATE_BITS)) & (LEVEL_COUNT - 1); ++ final int posX = ((int) queueValue & (COORDINATE_SIZE - 1)) + decodeOffsetX; ++ final int posZ = (((int) queueValue >>> COORDINATE_BITS) & (COORDINATE_SIZE - 1)) + decodeOffsetZ; ++ final int propagatedLevel = ((int) queueValue >>> (COORDINATE_BITS + COORDINATE_BITS)) & (LEVEL_COUNT - 1); + // note: the above code requires coordinate bits * 2 < 32 + // bitset is 16 bits +- int propagateDirectionBitset = (int)(queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); ++ int propagateDirectionBitset = (int) (queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); + + // this bitset represents the values that we have not propagated to + // this bitset lets us determine what directions the neighbours we set should propagate to, in most cases +@@ -1172,8 +1186,8 @@ public abstract class ThreadedTicketLevelPropagator { + // must guarantee that either we propagate everything in 1 radius or we partially propagate for 1 radius + // but the rest not propagated are already handled + long currentPropagation = ~( +- // z = -1 +- (1L << ((2 - 1) | ((2 - 1) << 3))) | ++ // z = -1 ++ (1L << ((2 - 1) | ((2 - 1) << 3))) | + (1L << ((2 + 0) | ((2 - 1) << 3))) | + (1L << ((2 + 1) | ((2 - 1) << 3))) | + +@@ -1229,7 +1243,7 @@ public abstract class ThreadedTicketLevelPropagator { + final long bitsetLine3 = currentPropagation & (7L << (start + (8 + 8))); + + // now try to propagate +- final Section section = this.sections[sectionIndex]; ++ final Section section = sectionsArray[sectionIndex]; + + // lower 8 bits are current level, next upper 7 bits are source level, next 1 bit is updated source flag + final short currentStoredLevel = section.levels[localIndex]; +@@ -1246,7 +1260,7 @@ public abstract class ThreadedTicketLevelPropagator { + increaseQueue = this.resizeIncreaseQueue(); + } + increaseQueue[increaseQueueLength++] = +- ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | ++ ((long) (offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | + ((currentLevel & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | + (FLAG_RECHECK_LEVEL | (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS))); + continue; +@@ -1257,8 +1271,8 @@ public abstract class ThreadedTicketLevelPropagator { + //currentPropagation ^= (bitsetLine1 | bitsetLine2 | bitsetLine3); + + // update level +- section.levels[localIndex] = (short)((currentStoredLevel & ~0xFF)); +- updatedPositions.putAndMoveToLast(CoordinateUtils.getChunkKey(offX, offZ), (byte)0); ++ section.levels[localIndex] = (short) ((currentStoredLevel & ~0xFF)); ++ updatedPositions.putAndMoveToLast(CoordinateUtils.getChunkKey(offX, offZ), (byte) 0); + + if (sourceLevel != 0) { + // re-propagate source +@@ -1267,7 +1281,7 @@ public abstract class ThreadedTicketLevelPropagator { + increaseQueue = this.resizeIncreaseQueue(); + } + increaseQueue[increaseQueueLength++] = +- ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | ++ ((long) (offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | + ((sourceLevel & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | + (FLAG_WRITE_LEVEL | (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS))); + } +@@ -1278,7 +1292,7 @@ public abstract class ThreadedTicketLevelPropagator { + // the child bitset is 4x4, so we just shift each line by 4 + // add the propagation bitset offset to each line to make it easy to OR it into the propagation queue value + final long childPropagation = +- ((bitsetLine1 >>> (start)) << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = -1 ++ ((bitsetLine1 >>> (start)) << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = -1 + ((bitsetLine2 >>> (start + 8)) << (4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = 0 + ((bitsetLine3 >>> (start + (8 + 8))) << (4 + 4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); // z = 1 + +@@ -1288,7 +1302,7 @@ public abstract class ThreadedTicketLevelPropagator { + queue = this.resizeDecreaseQueue(); + } + queue[queueLength++] = +- ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | ++ ((long) (offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | + ((toPropagate & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | + (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); //childPropagation; + continue; diff --git a/leaf-server/minecraft-patches/features/0158-Optimise-MobEffectUtil-getDigSpeedAmplification.patch b/leaf-server/minecraft-patches/features/0158-Optimise-MobEffectUtil-getDigSpeedAmplification.patch new file mode 100644 index 00000000..3550d539 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0158-Optimise-MobEffectUtil-getDigSpeedAmplification.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Mon, 14 Apr 2025 18:07:21 +0200 +Subject: [PATCH] Optimise MobEffectUtil#getDigSpeedAmplification + + +diff --git a/net/minecraft/world/effect/MobEffectUtil.java b/net/minecraft/world/effect/MobEffectUtil.java +index cbf1b6af928aa439c3264b302e5f1a1ddd4c14f0..c59a503ef8bc2dabcf9f7c85c8d93fb1fcadf71f 100644 +--- a/net/minecraft/world/effect/MobEffectUtil.java ++++ b/net/minecraft/world/effect/MobEffectUtil.java +@@ -29,12 +29,14 @@ public final class MobEffectUtil { + public static int getDigSpeedAmplification(LivingEntity entity) { + int i = 0; + int i1 = 0; +- if (entity.hasEffect(MobEffects.DIG_SPEED)) { +- i = entity.getEffect(MobEffects.DIG_SPEED).getAmplifier(); ++ MobEffectInstance digEffect = entity.getEffect(MobEffects.DIG_SPEED); ++ if (digEffect != null) { ++ i = digEffect.getAmplifier(); + } + +- if (entity.hasEffect(MobEffects.CONDUIT_POWER)) { +- i1 = entity.getEffect(MobEffects.CONDUIT_POWER).getAmplifier(); ++ MobEffectInstance conduitEffect = entity.getEffect(MobEffects.CONDUIT_POWER); ++ if (conduitEffect != null) { ++ i1 = conduitEffect.getAmplifier(); + } + + return Math.max(i, i1); diff --git a/leaf-server/minecraft-patches/features/0135-Optimize-chunkUnload.patch b/leaf-server/minecraft-patches/features/0159-Optimise-chunkUnloads.patch similarity index 51% rename from leaf-server/minecraft-patches/features/0135-Optimize-chunkUnload.patch rename to leaf-server/minecraft-patches/features/0159-Optimise-chunkUnloads.patch index ce7a01ab..d9564a21 100644 --- a/leaf-server/minecraft-patches/features/0135-Optimize-chunkUnload.patch +++ b/leaf-server/minecraft-patches/features/0159-Optimise-chunkUnloads.patch @@ -1,9 +1,47 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Taiyou06 -Date: Fri, 28 Feb 2025 01:35:49 +0100 -Subject: [PATCH] Optimize chunkUnload +Date: Mon, 14 Apr 2025 20:07:52 +0200 +Subject: [PATCH] Optimise chunkUnloads +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java +index e4a5fa25ed368fc4662c30934da2963ef446d782..62ad5fe1196cd982b0d48b1e4903d036262ac54b 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java +@@ -1753,23 +1753,20 @@ public final class NewChunkHolder { + chunk.tryMarkSaved(); + + final CallbackCompletable completable = new CallbackCompletable<>(); +- +- final Runnable run = () -> { +- final CompoundTag data = chunkData.write(); +- +- completable.complete(data); +- +- if (unloading) { +- NewChunkHolder.this.completeAsyncUnloadDataSave(MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, data); +- } +- }; +- + final PrioritisedExecutor.PrioritisedTask task; ++ + if (unloading) { +- this.chunkDataUnload.toRun().setRunnable(run); ++ this.chunkDataUnload.toRun().setRunnable(() -> { ++ final CompoundTag data = chunkData.write(); ++ completable.complete(data); ++ NewChunkHolder.this.completeAsyncUnloadDataSave(MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, data); ++ }); + task = this.chunkDataUnload.task(); + } else { +- task = this.scheduler.saveExecutor.createTask(run); ++ task = this.scheduler.saveExecutor.createTask(() -> { ++ final CompoundTag data = chunkData.write(); ++ completable.complete(data); ++ }); + } + + task.queue(); diff --git a/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java b/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java index 4ca68a903e67606fc4ef0bfa9862a73797121c8b..bed3a64388bb43e47c2ba4e67f7dde5b990d9578 100644 --- a/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java @@ -211,3 +249,128 @@ index b8ac6a9ba7b56ccd034757f7d135d272b8e69e90..dc158e981199b507531af810ff9ced3c return new LevelChunkSection(this); } } +diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +index e9ece9b618b0a9eb82b9f07a09ee6cb60cf7ec16..18d2ec110fc6670edb079eccf448389dc365eb88 100644 +--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java ++++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +@@ -526,14 +526,14 @@ public record SerializableChunkData( + throw new IllegalArgumentException("Chunk can't be serialized: " + chunk); + } else { + ChunkPos pos = chunk.getPos(); +- List list = new ArrayList<>(); final List sectionsList = list; // Paper - starlight - OBFHELPER +- LevelChunkSection[] sections = chunk.getSections(); +- LevelLightEngine lightEngine = level.getChunkSource().getLightEngine(); + + // Paper start - starlight + final int minLightSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinLightSection(level); + final int maxLightSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxLightSection(level); + final int minBlockSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(level); ++ // Pre-allocate with correct capacity to avoid resizing ++ final int expectedSectionCount = maxLightSection - minLightSection + 1; ++ List list = new ArrayList<>(expectedSectionCount); + + final LevelChunkSection[] chunkSections = chunk.getSections(); + final ca.spottedleaf.moonrise.patches.starlight.light.SWMRNibbleArray[] blockNibbles = ((ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk)chunk).starlight$getBlockNibbles(); +@@ -551,10 +551,18 @@ public record SerializableChunkData( + continue; + } + ++ DataLayer blockDataLayer = null; ++ if (blockNibble != null && blockNibble.data != null) { ++ blockDataLayer = new DataLayer(blockNibble.data); ++ } ++ ++ DataLayer skyDataLayer = null; ++ if (skyNibble != null && skyNibble.data != null) { ++ skyDataLayer = new DataLayer(skyNibble.data); ++ } ++ + final SerializableChunkData.SectionData sectionData = new SerializableChunkData.SectionData( +- lightSection, chunkSection, +- blockNibble == null ? null : (blockNibble.data == null ? null : new DataLayer(blockNibble.data)), +- skyNibble == null ? null : (skyNibble.data == null ? null : new DataLayer(skyNibble.data)) ++ lightSection, chunkSection, blockDataLayer, skyDataLayer + ); + + if (blockNibble != null) { +@@ -565,28 +573,42 @@ public record SerializableChunkData( + ((ca.spottedleaf.moonrise.patches.starlight.storage.StarlightSectionData)(Object)sectionData).starlight$setSkyLightState(skyNibble.state); + } + +- sectionsList.add(sectionData); ++ list.add(sectionData); + } + // Paper end - starlight + +- List list1 = new ArrayList<>(chunk.getBlockEntitiesPos().size()); ++ // Pre-allocate block entities list with exact size needed ++ final int blockEntityCount = chunk.getBlockEntitiesPos().size(); ++ List list1 = blockEntityCount > 0 ? new ArrayList<>(blockEntityCount) : java.util.Collections.emptyList(); + +- for (BlockPos blockPos : chunk.getBlockEntitiesPos()) { +- CompoundTag blockEntityNbtForSaving = chunk.getBlockEntityNbtForSaving(blockPos, level.registryAccess()); +- if (blockEntityNbtForSaving != null) { +- list1.add(blockEntityNbtForSaving); ++ if (blockEntityCount > 0) { ++ for (BlockPos blockPos : chunk.getBlockEntitiesPos()) { ++ CompoundTag blockEntityNbtForSaving = chunk.getBlockEntityNbtForSaving(blockPos, level.registryAccess()); ++ if (blockEntityNbtForSaving != null) { ++ list1.add(blockEntityNbtForSaving); ++ } + } + } + +- List list2 = new ArrayList<>(); ++ // For entities, use an initial estimated capacity if it's a ProtoChunk ++ List list2; + long[] longs = null; ++ + if (chunk.getPersistedStatus().getChunkType() == ChunkType.PROTOCHUNK) { + ProtoChunk protoChunk = (ProtoChunk)chunk; +- list2.addAll(protoChunk.getEntities()); ++ int entitySize = protoChunk.getEntities().size(); ++ if (entitySize > 0) { ++ list2 = new ArrayList<>(Math.max(16, entitySize)); ++ list2.addAll(protoChunk.getEntities()); ++ } else { ++ list2 = java.util.Collections.emptyList(); ++ } + CarvingMask carvingMask = protoChunk.getCarvingMask(); + if (carvingMask != null) { + longs = carvingMask.toArray(); + } ++ } else { ++ list2 = java.util.Collections.emptyList(); + } + + Map map = new EnumMap<>(Heightmap.Types.class); +@@ -594,14 +616,25 @@ public record SerializableChunkData( + for (Entry entry : chunk.getHeightmaps()) { + if (chunk.getPersistedStatus().heightmapsAfter().contains(entry.getKey())) { + long[] rawData = entry.getValue().getRawData(); +- map.put(entry.getKey(), (long[])rawData.clone()); ++ map.put(entry.getKey(), Arrays.copyOf(rawData, rawData.length)); + } + } + + ChunkAccess.PackedTicks ticksForSerialization = chunk.getTicksForSerialization(level.getGameTime()); +- ShortList[] lists = Arrays.stream(chunk.getPostProcessing()) +- .map(list3 -> list3 != null ? new ShortArrayList(list3) : null) +- .toArray(ShortList[]::new); ++ // Leaf start - Some Optimizations on SerializableChunkData ++ ShortList[] postProcessing = chunk.getPostProcessing(); ++ ShortList[] lists = new ShortList[postProcessing.length]; ++ for (int i = 0; i < postProcessing.length; i++) { ++ ShortList source = postProcessing[i]; ++ // Only create a new list if there's actual data to copy ++ if (source != null) { ++ int size = source.size(); ++ if (size > 0) { ++ lists[i] = new ShortArrayList(size); ++ lists[i].addAll(source); ++ } ++ } ++ } + CompoundTag compoundTag = packStructureData( + StructurePieceSerializationContext.fromLevel(level), pos, chunk.getAllStarts(), chunk.getAllReferences() + ); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/map/spottedleaf/LeafConcurrentLong2ReferenceChainedHashTable.java b/leaf-server/src/main/java/org/dreeam/leaf/util/map/spottedleaf/LeafConcurrentLong2ReferenceChainedHashTable.java new file mode 100644 index 00000000..2e6719b8 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/map/spottedleaf/LeafConcurrentLong2ReferenceChainedHashTable.java @@ -0,0 +1,2102 @@ +package org.dreeam.leaf.util.map.spottedleaf; + +import ca.spottedleaf.concurrentutil.function.BiLong1Function; +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; +import ca.spottedleaf.concurrentutil.util.HashUtil; +import ca.spottedleaf.concurrentutil.util.IntegerUtil; +import ca.spottedleaf.concurrentutil.util.ThrowUtil; +import ca.spottedleaf.concurrentutil.util.Validate; + +import java.lang.invoke.VarHandle; +import java.util.*; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.LongAdder; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.LongConsumer; +import java.util.function.LongFunction; +import java.util.function.Predicate; + +/** + * Optimized concurrent hashtable implementation supporting mapping arbitrary {@code long} keys onto non-null {@code Object} + * values with support for multiple writer and multiple reader threads. Utilizes lock-free read paths, + * optimistic lock-free write attempts, and fine-grained locking during modifications and resizing. + * + *

Happens-before relationship

+ *

+ * As with {@link ConcurrentMap}, actions in a thread prior to placing an object into this map + * happen-before actions subsequent to the access or removal of that object in another thread. + *

+ * + *

Atomicity of functional methods

+ *

+ * Functional methods (like {@code compute}, {@code merge}, etc.) are performed atomically per key. + * The function provided is guaranteed to be invoked at most once per call under a lock specific to the + * entry's bin. Consequently, invoking other map modification methods on this map from within the function + * can lead to undefined behavior or deadlock. + *

+ * + * @param The type of mapped values (must be non-null). + * @see java.util.concurrent.ConcurrentHashMap + */ + +public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable> { + + // --- Constants --- + + protected static final int DEFAULT_CAPACITY = 16; + protected static final float DEFAULT_LOAD_FACTOR = 0.75f; + /** The maximum capacity, used if a higher value is implicitly specified by either + * of the constructors with arguments. MUST be a power of two <= 1<<30. + */ + protected static final int MAXIMUM_CAPACITY = 1 << 30; // 2^30 + + protected static final int THRESHOLD_NO_RESIZE = -1; // Sentinel value: table cannot be resized + protected static final int THRESHOLD_RESIZING = -2; // Sentinel value: table is currently resizing + + // --- Instance Fields --- + + /** Tracks the number of mappings, using LongAdder for better high-contention performance. */ + protected final LongAdder size = new LongAdder(); + + /** The load factor for the hash table. */ + protected final float loadFactor; + + /** The hash table array. Elements are accessed using VarHandles. */ + protected volatile TableEntry[] table; + + /** + * The next size value at which to resize (unless {@code <= 0}). + * Accessed via VarHandle {@link #THRESHOLD_HANDLE}. + */ + protected volatile int threshold; + + // --- VarHandles --- + + protected static final VarHandle THRESHOLD_HANDLE; + static { + try { + THRESHOLD_HANDLE = ConcurrentUtil.getVarHandle(LeafConcurrentLong2ReferenceChainedHashTable.class, "threshold", int.class); + } catch (Throwable t) { + throw new Error("Failed to initialize VarHandles", t); + } + // Static initialization for TableEntry VarHandles is inside the TableEntry class + } + + // --- Views (lazily initialized) --- + + protected transient Values values; + protected transient EntrySet entrySet; + + // --- Constructors --- + + /** + * Creates a new, empty map with the default initial capacity (16) and load factor (0.75). + */ + public LeafConcurrentLong2ReferenceChainedHashTable() { + this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); + } + + /** + * Creates a new, empty map with the specified initial capacity and load factor. + * + * @param initialCapacity The initial capacity. The implementation performs internal + * sizing to accommodate this many elements. + * @param loadFactor The load factor threshold, used to control resizing. + * @throws IllegalArgumentException if the initial capacity is negative or the load + * factor is non-positive or NaN. + */ + @SuppressWarnings("unchecked") + protected LeafConcurrentLong2ReferenceChainedHashTable(final int initialCapacity, final float loadFactor) { + if (loadFactor <= 0.0f || !Float.isFinite(loadFactor)) { + throw new IllegalArgumentException("Invalid load factor: " + loadFactor); + } + if (initialCapacity < 0) { + throw new IllegalArgumentException("Invalid initial capacity: " + initialCapacity); + } + + final int tableSize = getCapacityFor(initialCapacity); + this.loadFactor = loadFactor; + this.setThresholdPlain(getTargetThreshold(tableSize, loadFactor)); // Use plain set, happens-before established by volatile table write + this.table = (TableEntry[]) new TableEntry[tableSize]; // Volatile write publishes the initial state + } + + /** + * Creates a new, empty map with the specified initial capacity and the default load factor (0.75). + * + * @param capacity The initial capacity. + * @throws IllegalArgumentException if the initial capacity is negative. + */ + public static LeafConcurrentLong2ReferenceChainedHashTable createWithCapacity(final int capacity) { + return createWithCapacity(capacity, DEFAULT_LOAD_FACTOR); + } + + /** + * Creates a new, empty map with the specified initial capacity and load factor. + * + * @param capacity The initial capacity. + * @param loadFactor The load factor threshold. + * @throws IllegalArgumentException if the initial capacity is negative or the load factor is non-positive/NaN. + */ + public static LeafConcurrentLong2ReferenceChainedHashTable createWithCapacity(final int capacity, final float loadFactor) { + return new LeafConcurrentLong2ReferenceChainedHashTable<>(capacity, loadFactor); + } + + /** + * Creates a new, empty map with an initial capacity sufficient to hold the specified number of elements + * without resizing, using the default load factor (0.75). + * + * @param expected The expected number of elements. + * @throws IllegalArgumentException if the expected size is negative. + */ + public static LeafConcurrentLong2ReferenceChainedHashTable createWithExpected(final int expected) { + return createWithExpected(expected, DEFAULT_LOAD_FACTOR); + } + + /** + * Creates a new, empty map with an initial capacity sufficient to hold the specified number of elements + * without resizing, using the specified load factor. + * + * @param expected The expected number of elements. + * @param loadFactor The load factor threshold. + * @throws IllegalArgumentException if the expected size is negative or the load factor is non-positive/NaN. + */ + public static LeafConcurrentLong2ReferenceChainedHashTable createWithExpected(final int expected, final float loadFactor) { + if (expected < 0) { + throw new IllegalArgumentException("Invalid expected size: " + expected); + } + // Calculate initial capacity based on expected size and load factor + final double capacityEstimate = ((double) expected / (double) loadFactor) + 1.0; + final int capacity = (capacityEstimate >= (double) MAXIMUM_CAPACITY) + ? MAXIMUM_CAPACITY + : (int) Math.min(MAXIMUM_CAPACITY, Math.max(DEFAULT_CAPACITY, Math.ceil(capacityEstimate))); + return createWithCapacity(capacity, loadFactor); + } + + // --- Internal Helper Methods --- + + /** Calculates the target resize threshold. */ + protected static int getTargetThreshold(final int capacity, final float loadFactor) { + if (capacity >= MAXIMUM_CAPACITY) { + return THRESHOLD_NO_RESIZE; // Max capacity reached, no more resizing + } + // Calculate threshold, preventing overflow and ensuring it's at least 1 + final double calculatedThreshold = (double) capacity * (double) loadFactor; + if (calculatedThreshold >= (double) MAXIMUM_CAPACITY) { + return MAXIMUM_CAPACITY; // Cap threshold at maximum capacity if calculation exceeds it + } + // Use ceil to ensure threshold is met strictly *after* the size reaches it + return (int) Math.max(1, Math.ceil(calculatedThreshold)); + } + + + /** Calculates the power-of-two capacity for a given initial capacity request. */ + protected static int getCapacityFor(final int requestedCapacity) { + if (requestedCapacity <= 0) { + // Default capacity if non-positive requested, could also throw exception + return DEFAULT_CAPACITY; + } + if (requestedCapacity >= MAXIMUM_CAPACITY) { + return MAXIMUM_CAPACITY; + } + // Round up to the next power of two + return IntegerUtil.roundCeilLog2(Math.max(DEFAULT_CAPACITY, requestedCapacity)); + } + + /** Computes the hash code for the key. Uses mixing to spread keys more evenly. */ + protected static int getHash(final long key) { + return (int) HashUtil.mix(key); // Assumes HashUtil.mix provides good distribution + } + + /** Returns the load factor associated with this map. */ + public final float getLoadFactor() { + return this.loadFactor; + } + + // --- VarHandle Accessors for 'threshold' --- + + protected final int getThresholdAcquire() { + return (int) THRESHOLD_HANDLE.getAcquire(this); + } + + protected final int getThresholdVolatile() { + return (int) THRESHOLD_HANDLE.getVolatile(this); + } + + protected final void setThresholdPlain(final int threshold) { + THRESHOLD_HANDLE.set(this, threshold); + } + + protected final void setThresholdRelease(final int threshold) { + THRESHOLD_HANDLE.setRelease(this, threshold); + } + + protected final void setThresholdVolatile(final int threshold) { + THRESHOLD_HANDLE.setVolatile(this, threshold); + } + + protected final int compareExchangeThresholdVolatile(final int expect, final int update) { + return (int) THRESHOLD_HANDLE.compareAndExchange(this, expect, update); + } + + // --- VarHandle Accessors for 'table' array elements --- + + @SuppressWarnings("unchecked") + protected static TableEntry getAtIndexVolatile(final TableEntry[] table, final int index) { + return (TableEntry) TableEntry.TABLE_ENTRY_ARRAY_HANDLE.getVolatile(table, index); + } + + protected static void setAtIndexRelease(final TableEntry[] table, final int index, final TableEntry value) { + TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setRelease(table, index, value); + } + + protected static void setAtIndexVolatile(final TableEntry[] table, final int index, final TableEntry value) { + TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setVolatile(table, index, value); + } + + @SuppressWarnings("unchecked") + protected static TableEntry compareAndExchangeAtIndexVolatile(final TableEntry[] table, final int index, + final TableEntry expect, final TableEntry update) { + return (TableEntry) TableEntry.TABLE_ENTRY_ARRAY_HANDLE.compareAndExchange(table, index, expect, update); + } + + // --- Core Map Operations --- + + /** + * Retrieves the node associated with the key. This is the core lookup logic. + * It handles concurrent resizes without locking for reads. + * Returns null if the key is not found. + * The returned node's value might be null if it's a placeholder during a compute operation. + */ + @SuppressWarnings("unchecked") + protected final TableEntry getNode(final long key) { + final int hash = getHash(key); + TableEntry[] currentTable = this.table; // Volatile read + + outer_loop: + for (;;) { // Loop handles table resizes detected during traversal + final int tableLength = currentTable.length; + if (tableLength == 0) { + // Table might not be initialized yet (race in constructor?), re-read. + currentTable = this.table; + if (currentTable.length == 0) { + // Still not initialized? Should not happen normally. Return null safely. + return null; + } + continue; // Retry with the initialized table + } + + final int index = hash & (tableLength - 1); // Calculate index using mask + TableEntry head = getAtIndexVolatile(currentTable, index); // Volatile read of bin head + + if (head == null) { + return null; // Bin is empty + } + + // Check if the bin head is a resize marker + if (head.isResizeMarker()) { + currentTable = helpResizeOrGetNextTable(currentTable, head); + continue outer_loop; // Retry operation with the new table + } + + // Check if the head node itself contains the key + // Reduces chain traversal for head hits + if (head.key == key) { + return head; + } + + // Traverse the linked list (chain) in the bin + // Volatile read is necessary here to observe concurrent modifications (removals/resizes) + TableEntry node = head.getNextVolatile(); + while (node != null) { + if (node.key == key) { + return node; // Key found + } + node = node.getNextVolatile(); // Move to the next node using volatile read + } + + // Key not found in the chain. + // Crucial check: Re-read table reference to see if a resize occurred *during* traversal. + TableEntry[] latestTable = this.table; // Volatile read + if (currentTable != latestTable) { + // Table reference changed, a resize happened. Retry the whole lookup. + currentTable = latestTable; + continue outer_loop; + } + + // Key not found, and table reference is stable since traversal started. + return null; + } + } + + /** + * Helps with resizing or gets the reference to the next table if the current + * bin contains a resize marker. + */ + @SuppressWarnings("unchecked") + private TableEntry[] helpResizeOrGetNextTable(TableEntry[] currentTable, TableEntry resizeMarker) { + // The new table reference is stored in the 'value' field of the resize marker + V markerValue = resizeMarker.getValuePlain(); // Plain read is safe, marker itself is effectively final + if (markerValue instanceof TableEntry[]) { + // Consider adding active resizing help here in a contended scenario + return (TableEntry[]) markerValue; + } + // Fallback: Should not happen if markers are correct. Force retry by re-reading table. + return this.table; + } + + + /** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + * @param key the key whose associated value is to be returned + * @return the value mapped to the key, or {@code null} if none + */ + public V get(final long key) { + final TableEntry node = this.getNode(key); + // Use volatile read on value to ensure happens-before visibility + return (node == null) ? null : node.getValueVolatile(); + } + + /** + * Returns the value to which the specified key is mapped, or + * {@code defaultValue} if this map contains no mapping for the key. + * + * @param key the key whose associated value is to be returned + * @param defaultValue the default mapping of the key + * @return the value mapped to the key, or {@code defaultValue} if none + */ + public V getOrDefault(final long key, final V defaultValue) { + final TableEntry node = this.getNode(key); + if (node == null) { + return defaultValue; + } + // Use volatile read for visibility. Check for null in case it's a compute placeholder. + final V value = node.getValueVolatile(); + return (value == null) ? defaultValue : value; + } + + /** + * Returns {@code true} if this map contains a mapping for the specified key. + * + * @param key The key whose presence in this map is to be tested + * @return {@code true} if this map contains a mapping for the specified key + */ + public boolean containsKey(final long key) { + final TableEntry node = this.getNode(key); + // Must check value is non-null, as getNode might return a placeholder + return node != null && node.getValueVolatile() != null; // Volatile read for visibility + } + + /** + * Returns {@code true} if this map maps one or more keys to the specified value. + * Note: This operation requires traversing the entire map. + * + * @param value value whose presence in this map is to be tested + * @return {@code true} if this map maps one or more keys to the specified value + * @throws NullPointerException if the specified value is null + */ + public boolean containsValue(final V value) { + Validate.notNull(value, "Value cannot be null"); + // Use an iterator that handles concurrent modifications and resizes safely. + NodeIterator iterator = new NodeIterator<>(this.table, this); + TableEntry node; + while ((node = iterator.findNext()) != null) { // findNext safely iterates through nodes + V nodeValue = node.getValueVolatile(); // Volatile read for visibility + if (nodeValue != null && value.equals(nodeValue)) { + return true; + } + } + return false; + } + + /** + * Returns the number of key-value mappings in this map. If the + * number of elements exceeds {@code Integer.MAX_VALUE}, returns + * {@code Integer.MAX_VALUE}. + * + * @return the number of key-value mappings in this map + */ + public int size() { + final long ret = this.size.sum(); + // Cap the size at Integer.MAX_VALUE as per ConcurrentMap contract + return (ret >= (long) Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int) ret; + } + + /** + * Returns {@code true} if this map contains no key-value mappings. + * + * @return {@code true} if this map contains no key-value mappings + */ + public boolean isEmpty() { + // Check size first for a quick exit, but verify with iteration if size is 0 + // as LongAdder.sum() might be transiently inaccurate. + if (this.size.sum() > 0L) { + return false; + } + // If size reports 0, double-check by looking for any actual node + NodeIterator it = new NodeIterator<>(this.table, this); + return it.findNext() == null; + } + + /** + * Increments the size count and initiates resizing if the threshold is exceeded. + */ + protected final void addSize(final long count) { + this.size.add(count); + int currentThreshold; + do { + currentThreshold = this.getThresholdAcquire(); // Acquire fence for reading threshold + if (currentThreshold <= 0) return; // THRESHOLD_NO_RESIZE or THRESHOLD_RESIZING + + final long currentSum = this.size.sum(); // Get current estimated size + if (currentSum < (long) currentThreshold) { + // Double check threshold hasn't changed due to another thread finishing resize + if (currentThreshold == this.getThresholdVolatile()) return; + continue; // Threshold changed, retry the loop + } + + // Size exceeds threshold, attempt to initiate resize + if (this.compareExchangeThresholdVolatile(currentThreshold, THRESHOLD_RESIZING) == currentThreshold) { + this.resize(currentSum); // Pass estimated size + return; // Resize initiated or completed + } + // CAS failed, another thread initiated resize. Loop might retry. + } while (true); + } + + /** + * Decrements the size count. + */ + protected final void subSize(final long count) { + this.size.add(-count); + // Note: No resize check needed on removal + } + + /** + * Resizes the table to accommodate more entries. Called by the thread + * that successfully sets the threshold to THRESHOLD_RESIZING. + */ + @SuppressWarnings("unchecked") + private void resize(final long estimatedSize) { // estimatedSize might not be perfectly accurate + final TableEntry[] oldTable = this.table; // Volatile read + final int oldCapacity = oldTable.length; + + if (oldCapacity >= MAXIMUM_CAPACITY) { + this.setThresholdVolatile(THRESHOLD_NO_RESIZE); + return; + } + + int newCapacity = oldCapacity << 1; // Double the capacity + if (newCapacity <= oldCapacity || newCapacity > MAXIMUM_CAPACITY) { // Handle overflow or max + newCapacity = MAXIMUM_CAPACITY; + } + if (newCapacity == oldCapacity) { // Already maxed out + this.setThresholdVolatile(THRESHOLD_NO_RESIZE); + return; + } + + final int newThreshold = getTargetThreshold(newCapacity, this.loadFactor); + final TableEntry[] newTable = (TableEntry[]) new TableEntry[newCapacity]; + final TableEntry resizeMarker = new TableEntry<>(0L, (V) newTable, true); // Key irrelevant for marker + + for (int i = 0; i < oldCapacity; ++i) { + TableEntry head = getAtIndexVolatile(oldTable, i); + + if (head == null) { + // Try to CAS marker into empty bin + if (compareAndExchangeAtIndexVolatile(oldTable, i, null, resizeMarker) == null) { + continue; // Marked empty bin + } + // CAS failed, re-read + head = getAtIndexVolatile(oldTable, i); + if (head == null || head.isResizeMarker()) continue; // Still null or handled + } + + if (head.isResizeMarker()) { + continue; // Already processed + } + + // Bin has entries, lock head to transfer chain + synchronized (head) { + TableEntry currentHead = getAtIndexVolatile(oldTable, i); + // Re-check after lock + if (currentHead != head) { + i--; // Reprocess index 'i' if head changed while waiting + continue; + } + if (head.isResizeMarker()) { + continue; // Marked while waiting + } + + // Split chain: index 'i' vs 'i + oldCapacity' + TableEntry lowH = null, lowT = null; + TableEntry highH = null, highT = null; + + TableEntry current = head; + while (current != null) { + TableEntry next = current.getNextPlain(); // Plain read inside lock + int hash = getHash(current.key); + + if ((hash & oldCapacity) == 0) { // Low bin (index i) + if (lowT == null) lowH = current; else lowT.setNextPlain(current); + lowT = current; + } else { // High bin (index i + oldCapacity) + if (highT == null) highH = current; else highT.setNextPlain(current); + highT = current; + } + current = next; + } + + if (lowT != null) lowT.setNextPlain(null); + if (highT != null) highT.setNextPlain(null); + + // Place chains into new table (volatile writes) + setAtIndexVolatile(newTable, i, lowH); + setAtIndexVolatile(newTable, i + oldCapacity, highH); + + // Mark old bin as processed (release write) + setAtIndexRelease(oldTable, i, resizeMarker); + } // End synchronized + } // End loop over old table bins + + // Finalize: publish new table and threshold + this.table = newTable; + this.setThresholdVolatile(newThreshold); + } + + + /** + * Maps the specified key to the specified value in this table. + * Neither the key nor the value can be null. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key}. + * @throws NullPointerException if the specified value is null + */ + public V put(final long key, final V value) { + Validate.notNull(value, "Value may not be null"); + final int hash = getHash(key); + int sizeDelta = 0; + V oldValue = null; + TableEntry[] currentTable = this.table; + + table_loop: + for (;;) { + final int tableLength = currentTable.length; + if (tableLength == 0) { currentTable = this.table; if (currentTable.length == 0) continue; } // Init check + + final int index = hash & (tableLength - 1); + TableEntry head = getAtIndexVolatile(currentTable, index); + + // Case 1: Bin is empty + if (head == null) { + TableEntry newNode = new TableEntry<>(key, value); + if (compareAndExchangeAtIndexVolatile(currentTable, index, null, newNode) == null) { + this.addSize(1L); + return null; // Inserted successfully + } + continue table_loop; // CAS failed, retry + } + + // Case 2: Resize marker + if (head.isResizeMarker()) { + currentTable = helpResizeOrGetNextTable(currentTable, head); + continue table_loop; + } + + // Case 3: Optimistic lock-free update attempt + TableEntry node = head; + while (node != null) { + if (node.key == key) { + V currentVal = node.getValueVolatile(); // Volatile read + if (currentVal == null) break; // Placeholder requires lock + // Try atomic update + if (node.compareAndSetValueVolatile(currentVal, value)) { + return currentVal; // Lock-free success + } + break; // CAS failed, need lock + } + node = node.getNextVolatile(); // Volatile read + } + + // Case 4: Locking path + synchronized (head) { + TableEntry currentHead = getAtIndexVolatile(currentTable, index); + // Re-check state after lock + if (currentHead != head || head.isResizeMarker()) { + continue table_loop; + } + + // Traverse again within lock + TableEntry prev = null; + node = head; + while (node != null) { + if (node.key == key) { + oldValue = node.getValuePlain(); // Plain read in lock + node.setValueVolatile(value); // Volatile write for visibility + sizeDelta = (oldValue == null) ? 1 : 0; // Adjust size if replacing placeholder + break table_loop; // Update done + } + prev = node; + node = node.getNextPlain(); // Plain read in lock + } + + // Key not found, add new node to end of chain + if (prev != null) { + TableEntry newNode = new TableEntry<>(key, value); + prev.setNextRelease(newNode); // Release write to link safely + sizeDelta = 1; + oldValue = null; + } else { + // Should not happen if head was non-null/non-marker. Retry. + continue table_loop; + } + } // End synchronized + break table_loop; // Operation completed within lock + } // End table_loop + + if (sizeDelta != 0) { + this.addSize(sizeDelta); + } + return oldValue; + } + + + /** + * If the specified key is not already associated with a value, associates + * it with the given value. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with the specified key, or + * {@code null} if there was no mapping for the key. + * @throws NullPointerException if the specified value is null + */ + public V putIfAbsent(final long key, final V value) { + Validate.notNull(value, "Value may not be null"); + final int hash = getHash(key); + int sizeDelta = 0; + V existingValue = null; + TableEntry[] currentTable = this.table; + + table_loop: + for(;;) { + final int tableLength = currentTable.length; + if (tableLength == 0) { currentTable = this.table; continue; } + + final int index = hash & (tableLength - 1); + TableEntry head = getAtIndexVolatile(currentTable, index); + + // Case 1: Bin is empty + if (head == null) { + TableEntry newNode = new TableEntry<>(key, value); + if (compareAndExchangeAtIndexVolatile(currentTable, index, null, newNode) == null) { + this.addSize(1L); + return null; // Inserted + } + continue table_loop; // CAS failed, retry + } + + // Case 2: Resize marker + if (head.isResizeMarker()) { + currentTable = helpResizeOrGetNextTable(currentTable, head); + continue table_loop; + } + + // Case 3: Lock-free check (optimistic) + TableEntry node = head; + while(node != null) { + if (node.key == key) { + existingValue = node.getValueVolatile(); // Volatile read + if (existingValue != null) { + return existingValue; // Key present with value + } + // Placeholder found, need lock + break; + } + node = node.getNextVolatile(); + } + + + // Case 4: Locking path + synchronized (head) { + TableEntry currentHead = getAtIndexVolatile(currentTable, index); + if (currentHead != head || head.isResizeMarker()) { + continue table_loop; // State changed, retry + } + + TableEntry prev = null; + node = head; + while (node != null) { + if (node.key == key) { + existingValue = node.getValuePlain(); // Plain read in lock + if (existingValue != null) { + break table_loop; // Return existing value + } else { + // Placeholder: update it + node.setValueVolatile(value); // Volatile write + sizeDelta = 1; + existingValue = null; // Return null as per contract + break table_loop; + } + } + prev = node; + node = node.getNextPlain(); // Plain read in lock + } + + // Key not found, add new node + if (prev != null) { + TableEntry newNode = new TableEntry<>(key, value); + prev.setNextRelease(newNode); // Release write + sizeDelta = 1; + existingValue = null; + } else { + continue table_loop; // Should not happen + } + } // End synchronized + break table_loop; + } // End table_loop + + if (sizeDelta != 0) { + this.addSize(sizeDelta); + } + return existingValue; + } + + /** + * Replaces the entry for a key only if currently mapped to some value. + * + * @param key key with which the specified value is associated + * @param value value to be associated with the specified key + * @return the previous value associated with the specified key, or + * {@code null} if there was no mapping for the key. + * @throws NullPointerException if the specified value is null + */ + public V replace(final long key, final V value) { + Validate.notNull(value, "Value may not be null"); + final int hash = getHash(key); + V oldValue = null; + TableEntry[] currentTable = this.table; + + table_loop: + for(;;) { + final int tableLength = currentTable.length; + if (tableLength == 0) return null; + + final int index = hash & (tableLength - 1); + TableEntry head = getAtIndexVolatile(currentTable, index); + + if (head == null) return null; + + if (head.isResizeMarker()) { + currentTable = helpResizeOrGetNextTable(currentTable, head); + continue table_loop; + } + + // Try Lock-Free Replace Attempt + TableEntry node = head; + while (node != null) { + if (node.key == key) { + do { // CAS retry loop + oldValue = node.getValueVolatile(); // Volatile read + if (oldValue == null) return null; // Cannot replace placeholder + + if (node.compareAndSetValueVolatile(oldValue, value)) { + return oldValue; // Lock-free success + } + // CAS failed, retry if key still matches + } while (node.key == key); + // Key changed or CAS keeps failing, fall back to lock + break; + } + node = node.getNextVolatile(); + } + + // Locking Path + synchronized (head) { + TableEntry currentHead = getAtIndexVolatile(currentTable, index); + if (currentHead != head || head.isResizeMarker()) { + continue table_loop; // Retry + } + node = head; + while (node != null) { + if (node.key == key) { + oldValue = node.getValuePlain(); // Plain read in lock + if (oldValue != null) { + node.setValueVolatile(value); // Volatile write + return oldValue; + } else { + return null; // Cannot replace placeholder + } + } + node = node.getNextPlain(); // Plain read in lock + } + } // End synchronized + + // Key not found after checks + return null; + } // End table_loop + } + + /** + * Replaces the entry for a key only if currently mapped to a given value. + * + * @param key key with which the specified value is associated + * @param expect value expected to be associated with the specified key + * @param update value to be associated with the specified key + * @return {@code true} if the value was replaced + * @throws NullPointerException if {@code expect} or {@code update} is null + */ + public boolean replace(final long key, final V expect, final V update) { + Validate.notNull(expect, "Expected value may not be null"); + Validate.notNull(update, "Update value may not be null"); + final int hash = getHash(key); + TableEntry[] currentTable = this.table; + + table_loop: + for(;;) { + final int tableLength = currentTable.length; + if (tableLength == 0) return false; + + final int index = hash & (tableLength - 1); + TableEntry head = getAtIndexVolatile(currentTable, index); + + if (head == null) return false; + + if (head.isResizeMarker()) { + currentTable = helpResizeOrGetNextTable(currentTable, head); + continue table_loop; + } + + // Lock-Free CAS Attempt + TableEntry node = head; + while (node != null) { + if (node.key == key) { + V currentVal = node.getValueVolatile(); // Volatile read + if (!Objects.equals(currentVal, expect)) { + return false; // Value doesn't match + } + // Value matches, try CAS + if (node.compareAndSetValueVolatile(expect, update)) { + return true; // Lock-free success + } + // CAS failed, need lock + break; + } + node = node.getNextVolatile(); + } + + // Locking Path + synchronized (head) { + TableEntry currentHead = getAtIndexVolatile(currentTable, index); + if (currentHead != head || head.isResizeMarker()) { + continue table_loop; // Retry + } + node = head; + while (node != null) { + if (node.key == key) { + V currentVal = node.getValuePlain(); // Plain read in lock + if (Objects.equals(currentVal, expect)) { + node.setValueVolatile(update); // Volatile write + return true; // Replaced successfully + } else { + return false; // Value doesn't match + } + } + node = node.getNextPlain(); // Plain read in lock + } + } // End synchronized + + // Key not found + return false; + } // End table_loop + } + + /** + * Removes the mapping for a key from this map if it is present. + * + * @param key key whose mapping is to be removed from the map + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key} + */ + public V remove(final long key) { + final int hash = getHash(key); + int sizeDelta = 0; + V oldValue = null; + TableEntry[] currentTable = this.table; + + table_loop: + for(;;) { + final int tableLength = currentTable.length; + if (tableLength == 0) return null; + + final int index = hash & (tableLength - 1); + TableEntry head = getAtIndexVolatile(currentTable, index); + + if (head == null) return null; + + if (head.isResizeMarker()) { + currentTable = helpResizeOrGetNextTable(currentTable, head); + continue table_loop; + } + + // Removal needs locking + synchronized (head) { + TableEntry currentHead = getAtIndexVolatile(currentTable, index); + if (currentHead != head || head.isResizeMarker()) { + continue table_loop; // Retry + } + + TableEntry prev = null; + TableEntry node = head; + while (node != null) { + if (node.key == key) { + oldValue = node.getValuePlain(); // Plain read in lock + sizeDelta = (oldValue != null) ? -1 : 0; // Decrement if actual mapping + + TableEntry next = node.getNextPlain(); // Plain read + // Update links with release semantics + if (prev == null) { + setAtIndexRelease(currentTable, index, next); // Removed head + } else { + prev.setNextRelease(next); // Removed middle/end + } + break table_loop; // Removed, exit loop + } + prev = node; + node = node.getNextPlain(); // Plain read + } + // Key not found in chain within lock + break table_loop; + } // End synchronized + } // End table_loop + + if (sizeDelta != 0) { + this.subSize(-sizeDelta); // subSize takes positive count + } + return oldValue; + } + + + /** + * Removes the entry for a key only if currently mapped to a given value. + * + * @param key key with which the specified value is associated + * @param expect value expected to be associated with the specified key + * @return {@code true} if the value was removed + */ + public boolean remove(final long key, final V expect) { + final int hash = getHash(key); + int sizeDelta = 0; + boolean removed = false; + TableEntry[] currentTable = this.table; + + table_loop: + for(;;) { + final int tableLength = currentTable.length; + if (tableLength == 0) return false; + + final int index = hash & (tableLength - 1); + TableEntry head = getAtIndexVolatile(currentTable, index); + + if (head == null) return false; + + if (head.isResizeMarker()) { + currentTable = helpResizeOrGetNextTable(currentTable, head); + continue table_loop; + } + + // Removal needs locking + synchronized (head) { + TableEntry currentHead = getAtIndexVolatile(currentTable, index); + if (currentHead != head || head.isResizeMarker()) { + continue table_loop; // Retry + } + + TableEntry prev = null; + TableEntry node = head; + while (node != null) { + if (node.key == key) { + V currentVal = node.getValuePlain(); // Plain read in lock + if (Objects.equals(currentVal, expect)) { // Safe comparison + removed = true; + sizeDelta = (currentVal != null) ? -1 : 0; // Decrement if actual value + + TableEntry next = node.getNextPlain(); // Plain read + // Update links with release semantics + if (prev == null) { + setAtIndexRelease(currentTable, index, next); + } else { + prev.setNextRelease(next); + } + } else { + removed = false; // Value didn't match + } + break table_loop; // Key processed + } + prev = node; + node = node.getNextPlain(); // Plain read + } + // Key not found in chain within lock + break table_loop; + } // End synchronized + } // End table_loop + + if (sizeDelta != 0) { + this.subSize(-sizeDelta); + } + return removed; + } + + /** + * Removes the entry for the specified key only if its value satisfies the given predicate. + * + * @param key key whose mapping is to be removed from the map + * @param predicate the predicate to apply to the value associated with the key + * @return the value associated with the key before removal if the predicate was satisfied and the entry was removed, + * otherwise {@code null}. + * @throws NullPointerException if the specified predicate is null + */ + public V removeIf(final long key, final Predicate predicate) { + Validate.notNull(predicate, "Predicate may not be null"); + final int hash = getHash(key); + int sizeDelta = 0; + V oldValue = null; + boolean removed = false; + TableEntry[] currentTable = this.table; + + table_loop: + for(;;) { + final int tableLength = currentTable.length; + if (tableLength == 0) return null; + + final int index = hash & (tableLength - 1); + TableEntry head = getAtIndexVolatile(currentTable, index); + + if (head == null) return null; + + if (head.isResizeMarker()) { + currentTable = helpResizeOrGetNextTable(currentTable, head); + continue table_loop; + } + + // Conditional removal needs locking + synchronized (head) { + TableEntry currentHead = getAtIndexVolatile(currentTable, index); + if (currentHead != head || head.isResizeMarker()) { + continue table_loop; // Retry + } + + TableEntry prev = null; + TableEntry node = head; + while (node != null) { + if (node.key == key) { + oldValue = node.getValuePlain(); // Plain read in lock + if (oldValue != null && predicate.test(oldValue)) { // Test non-null value + removed = true; + sizeDelta = -1; + + TableEntry next = node.getNextPlain(); // Plain read + // Update links with release semantics + if (prev == null) { + setAtIndexRelease(currentTable, index, next); + } else { + prev.setNextRelease(next); + } + } else { + removed = false; // Predicate failed or value null + } + break table_loop; // Key processed + } + prev = node; + node = node.getNextPlain(); // Plain read + } + // Key not found in chain within lock + break table_loop; + } // End synchronized + } // End table_loop + + if (sizeDelta != 0) { + this.subSize(-sizeDelta); + } + return removed ? oldValue : null; // Return old value only if removed + } + + // --- Compute Methods --- + + /** + * Attempts to compute a mapping for the specified key and its current mapped value + * (or {@code null} if there is no current mapping). The function is + * applied atomically. + * + * @param key key with which the specified value is to be associated + * @param function the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified function is null + */ + public V compute(final long key, final BiLong1Function function) { + Validate.notNull(function, "Function cannot be null"); + final int hash = getHash(key); + int sizeDelta = 0; + V finalValue = null; + TableEntry[] currentTable = this.table; + + table_loop: + for(;;) { + final int tableLength = currentTable.length; + if (tableLength == 0) { currentTable = this.table; continue; } + + final int index = hash & (tableLength - 1); + TableEntry head = getAtIndexVolatile(currentTable, index); + + // Case 1: Bin is empty. Use placeholder logic. + if (head == null) { + TableEntry placeholder = new TableEntry<>(key, null); // Temp node + V computedValue; + synchronized (placeholder) { // Lock placeholder for atomicity + if (getAtIndexVolatile(currentTable, index) == null) { // Re-check bin + try { + computedValue = function.apply(key, null); // Compute with null old value + } catch (Throwable t) { ThrowUtil.throwUnchecked(t); return null; } + + if (computedValue != null) { + placeholder.setValuePlain(computedValue); // Set value before CAS + // Attempt to insert the computed node + if (compareAndExchangeAtIndexVolatile(currentTable, index, null, placeholder) == null) { + sizeDelta = 1; + finalValue = computedValue; + break table_loop; // Success + } else { + continue table_loop; // CAS failed, retry + } + } else { + finalValue = null; // Computed null, no mapping + break table_loop; + } + } + } // End synchronized(placeholder) + continue table_loop; // Bin changed, retry + } // End Case 1 (head == null) + + // Case 2: Resize marker + if (head.isResizeMarker()) { + currentTable = helpResizeOrGetNextTable(currentTable, head); + continue table_loop; + } + + // Case 3: Bin not empty. Lock head. + synchronized (head) { + TableEntry currentHead = getAtIndexVolatile(currentTable, index); + if (currentHead != head || head.isResizeMarker()) { + continue table_loop; // Retry + } + + TableEntry prev = null; + TableEntry node = head; + while (node != null) { + if (node.key == key) { + // Key found. Compute with existing value. + V oldValue = node.getValuePlain(); // Plain read in lock + V computedValue; + try { + computedValue = function.apply(key, oldValue); + } catch (Throwable t) { ThrowUtil.throwUnchecked(t); return null; } + + if (computedValue != null) { + node.setValueVolatile(computedValue); // Update value (volatile write) + finalValue = computedValue; + sizeDelta = (oldValue == null) ? 1 : 0; // Size change if old was placeholder + } else { + // Remove mapping + finalValue = null; + sizeDelta = (oldValue != null) ? -1 : 0; // Size change only if old was value + TableEntry next = node.getNextPlain(); // Plain read + if (prev == null) setAtIndexRelease(currentTable, index, next); + else prev.setNextRelease(next); + } + break table_loop; // Done + } + prev = node; + node = node.getNextPlain(); // Plain read + } // End while + + // Key not found. Compute with null. + V computedValue; + try { + computedValue = function.apply(key, null); + } catch (Throwable t) { ThrowUtil.throwUnchecked(t); return null; } + + if (computedValue != null) { + // Add new mapping + finalValue = computedValue; + sizeDelta = 1; + TableEntry newNode = new TableEntry<>(key, computedValue); + if (prev != null) prev.setNextRelease(newNode); // Release write + else { continue table_loop; } // Should not happen + } else { + finalValue = null; + sizeDelta = 0; + } + break table_loop; // Done + } // End synchronized(head) + } // End table_loop + + if (sizeDelta > 0) this.addSize(sizeDelta); + else if (sizeDelta < 0) this.subSize(-sizeDelta); + + return finalValue; + } + + /** + * If the specified key is not already associated with a value, attempts to + * compute its value using the given mapping function and enters it into + * this map unless {@code null}. + * + * @param key key with which the specified value is to be associated + * @param function the function to compute a value + * @return the current (existing or computed) value associated with the specified key, + * or null if the computed value is null + * @throws NullPointerException if the specified function is null + */ + public V computeIfAbsent(final long key, final LongFunction function) { + Validate.notNull(function, "Function cannot be null"); + final int hash = getHash(key); + int sizeDelta = 0; + V finalValue = null; + TableEntry[] currentTable = this.table; + + table_loop: + for(;;) { + final int tableLength = currentTable.length; + if (tableLength == 0) { currentTable = this.table; continue; } + + final int index = hash & (tableLength - 1); + TableEntry head = getAtIndexVolatile(currentTable, index); + + // Case 1: Bin is empty. Use placeholder. + if (head == null) { + TableEntry placeholder = new TableEntry<>(key, null); + V computedValue; + synchronized (placeholder) { + if (getAtIndexVolatile(currentTable, index) == null) { + try { + computedValue = function.apply(key); + } catch (Throwable t) { ThrowUtil.throwUnchecked(t); return null; } + + if (computedValue != null) { + placeholder.setValuePlain(computedValue); + if (compareAndExchangeAtIndexVolatile(currentTable, index, null, placeholder) == null) { + sizeDelta = 1; + finalValue = computedValue; + break table_loop; // Inserted + } else { + continue table_loop; // CAS failed, retry + } + } else { + finalValue = null; // Computed null + break table_loop; + } + } + } // End synchronized(placeholder) + continue table_loop; // Bin changed, retry + } // End Case 1 + + // Case 2: Resize marker + if (head.isResizeMarker()) { + currentTable = helpResizeOrGetNextTable(currentTable, head); + continue table_loop; + } + + // Case 3: Lock-free check if key already exists with value + TableEntry node = head; + while (node != null) { + if (node.key == key) { + V existingValue = node.getValueVolatile(); // Volatile read + if (existingValue != null) { + return existingValue; // Already present + } + break; // Placeholder found, need lock + } + node = node.getNextVolatile(); + } + + // Case 4: Locking path + synchronized (head) { + TableEntry currentHead = getAtIndexVolatile(currentTable, index); + if (currentHead != head || head.isResizeMarker()) { + continue table_loop; // Retry + } + + TableEntry prev = null; + node = head; + while (node != null) { + if (node.key == key) { + V existingValue = node.getValuePlain(); // Plain read in lock + if (existingValue != null) { + finalValue = existingValue; // Found inside lock + } else { + // Placeholder exists, compute and update + V computedValue; + try { + computedValue = function.apply(key); + } catch (Throwable t) { ThrowUtil.throwUnchecked(t); return null; } + + if (computedValue != null) { + node.setValueVolatile(computedValue); // Volatile write + sizeDelta = 1; + finalValue = computedValue; + } else { + finalValue = null; // Computed null + } + } + break table_loop; // Done + } + prev = node; + node = node.getNextPlain(); // Plain read + } // End while + + // Key not found. Compute and add. + V computedValue; + try { + computedValue = function.apply(key); + } catch (Throwable t) { ThrowUtil.throwUnchecked(t); return null; } + + if (computedValue != null) { + finalValue = computedValue; + sizeDelta = 1; + TableEntry newNode = new TableEntry<>(key, computedValue); + if (prev != null) prev.setNextRelease(newNode); // Release write + else { continue table_loop; } // Should not happen + } else { + finalValue = null; + sizeDelta = 0; + } + break table_loop; // Done + } // End synchronized(head) + } // End table_loop + + if (sizeDelta > 0) this.addSize(sizeDelta); + return finalValue; + } + + + /** + * If the value for the specified key is present, attempts to compute a new + * mapping given the key and its current mapped value. + * + * @param key key with which the specified value is to be associated + * @param function the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified function is null + */ + public V computeIfPresent(final long key, final BiLong1Function function) { + Validate.notNull(function, "Function cannot be null"); + final int hash = getHash(key); + int sizeDelta = 0; + V finalValue = null; + TableEntry[] currentTable = this.table; + + table_loop: + for(;;) { + final int tableLength = currentTable.length; + if (tableLength == 0) return null; + + final int index = hash & (tableLength - 1); + TableEntry head = getAtIndexVolatile(currentTable, index); + + if (head == null) return null; + + if (head.isResizeMarker()) { + currentTable = helpResizeOrGetNextTable(currentTable, head); + continue table_loop; + } + + // Needs lock for potential removal + synchronized (head) { + TableEntry currentHead = getAtIndexVolatile(currentTable, index); + if (currentHead != head || head.isResizeMarker()) { + continue table_loop; // Retry + } + + TableEntry prev = null; + TableEntry node = head; + while (node != null) { + if (node.key == key) { + V oldValue = node.getValuePlain(); // Plain read in lock + if (oldValue != null) { // Only compute if value present + V computedValue; + try { + computedValue = function.apply(key, oldValue); + } catch (Throwable t) { ThrowUtil.throwUnchecked(t); return null; } + + if (computedValue != null) { + node.setValueVolatile(computedValue); // Update (volatile write) + finalValue = computedValue; + sizeDelta = 0; + } else { + // Remove mapping + finalValue = null; + sizeDelta = -1; + TableEntry next = node.getNextPlain(); // Plain read + if (prev == null) setAtIndexRelease(currentTable, index, next); + else prev.setNextRelease(next); + } + } else { + // Placeholder, treat as absent + finalValue = null; + sizeDelta = 0; + } + break table_loop; // Done + } + prev = node; + node = node.getNextPlain(); // Plain read + } // End while + + // Key not found + finalValue = null; + sizeDelta = 0; + break table_loop; + } // End synchronized(head) + } // End table_loop + + if (sizeDelta < 0) this.subSize(-sizeDelta); + return finalValue; + } + + /** + * If the specified key is not already associated with a value or is + * associated with null, associates it with the given non-null value. + * Otherwise, replaces the associated value with the results of the given + * remapping function, or removes if the result is {@code null}. + * + * @param key key with which the resulting value is to be associated + * @param value the non-null value to be merged with the existing value + * @param function the function to recompute a value if present + * @return the new value associated with the specified key, or null if no + * value is associated with the key + * @throws NullPointerException if the specified value or function is null + */ + public V merge(final long key, final V value, final BiFunction function) { + Validate.notNull(value, "Value cannot be null"); + Validate.notNull(function, "Function cannot be null"); + final int hash = getHash(key); + int sizeDelta = 0; + V finalValue = null; + TableEntry[] currentTable = this.table; + + table_loop: + for(;;) { + final int tableLength = currentTable.length; + if (tableLength == 0) { currentTable = this.table; continue; } + + final int index = hash & (tableLength - 1); + TableEntry head = getAtIndexVolatile(currentTable, index); + + // Case 1: Bin empty. Insert value. + if (head == null) { + TableEntry newNode = new TableEntry<>(key, value); + if (compareAndExchangeAtIndexVolatile(currentTable, index, null, newNode) == null) { + sizeDelta = 1; + finalValue = value; + break table_loop; // Inserted + } + continue table_loop; // CAS failed, retry + } + + // Case 2: Resize marker + if (head.isResizeMarker()) { + currentTable = helpResizeOrGetNextTable(currentTable, head); + continue table_loop; + } + + // Case 3: Lock head + synchronized (head) { + TableEntry currentHead = getAtIndexVolatile(currentTable, index); + if (currentHead != head || head.isResizeMarker()) { + continue table_loop; // Retry + } + + TableEntry prev = null; + TableEntry node = head; + while (node != null) { + if (node.key == key) { + // Key found. Merge. + V oldValue = node.getValuePlain(); // Plain read in lock + V computedValue; + if (oldValue != null) { + try { + computedValue = function.apply(oldValue, value); // Apply function + } catch (Throwable t) { ThrowUtil.throwUnchecked(t); return null; } + } else { + computedValue = value; // Use provided value if old was placeholder + } + + if (computedValue != null) { + node.setValueVolatile(computedValue); // Update (volatile write) + finalValue = computedValue; + sizeDelta = (oldValue == null) ? 1 : 0; // Size change if old was placeholder + } else { + // Remove mapping + finalValue = null; + sizeDelta = (oldValue != null) ? -1 : 0; // Size change if old was value + TableEntry next = node.getNextPlain(); // Plain read + if (prev == null) setAtIndexRelease(currentTable, index, next); + else prev.setNextRelease(next); + } + break table_loop; // Done + } + prev = node; + node = node.getNextPlain(); // Plain read + } // End while + + // Key not found. Add provided value. + finalValue = value; + sizeDelta = 1; + TableEntry newNode = new TableEntry<>(key, value); + if (prev != null) prev.setNextRelease(newNode); // Release write + else { continue table_loop; } // Should not happen + break table_loop; // Done + } // End synchronized(head) + } // End table_loop + + if (sizeDelta > 0) this.addSize(sizeDelta); + else if (sizeDelta < 0) this.subSize(-sizeDelta); + + return finalValue; + } + + + /** + * Removes all of the mappings from this map. + * The map will be empty after this call returns. + */ + public void clear() { + long removedCount = 0L; + TableEntry[] currentTable = this.table; // Volatile read + + for (int i = 0; i < currentTable.length; ++i) { + TableEntry head = getAtIndexVolatile(currentTable, i); + + if (head == null || head.isResizeMarker()) continue; + + // Lock bin to clear + synchronized (head) { + TableEntry currentHead = getAtIndexVolatile(currentTable, i); + // Re-check after lock + if (currentHead != head || head.isResizeMarker()) { + continue; // Bin changed, skip + } + + // Count actual mappings and clear bin + TableEntry node = head; + while (node != null) { + if (node.getValuePlain() != null) { // Count non-placeholders + removedCount++; + } + node = node.getNextPlain(); // Plain read in lock + } + // Clear bin head with release semantics + setAtIndexRelease(currentTable, i, null); + } // End synchronized + } // End loop + + if (removedCount > 0) { + this.subSize(removedCount); + } + } + + // --- Iterators and Views --- + + /** Returns an iterator over the map entries. */ + public Iterator> entryIterator() { return new EntryIterator<>(this); } + + /** Returns an iterator over the map entries (implements Iterable). */ + @Override public final Iterator> iterator() { return this.entryIterator(); } + + /** Returns an iterator over the keys. */ + public PrimitiveIterator.OfLong keyIterator() { return new KeyIterator<>(this); } + + /** Returns an iterator over the values. */ + public Iterator valueIterator() { return new ValueIterator<>(this); } + + /** + * Returns a {@link Collection} view of the values contained in this map. + */ + public Collection values() { + Values v = this.values; + return (v != null) ? v : (this.values = new Values<>(this)); + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. + */ + public Set> entrySet() { + EntrySet es = this.entrySet; + return (es != null) ? es : (this.entrySet = new EntrySet<>(this)); + } + + // --- Inner Classes: TableEntry, Iterators, Views --- + + /** + * Represents a key-value mapping entry in the hash table. + * Also used as a resize marker. + */ + public static final class TableEntry { + static final VarHandle TABLE_ENTRY_ARRAY_HANDLE; + private static final VarHandle VALUE_HANDLE; + private static final VarHandle NEXT_HANDLE; + + static { + try { + TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class); + VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class); + NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class); + } catch (Throwable t) { + throw new Error("Failed to initialize TableEntry VarHandles", t); + } + } + + final long key; + private volatile V value; + private volatile TableEntry next; + private final boolean resizeMarker; + + /** Constructor for regular map entries. */ + TableEntry(final long key, final V value) { + this(key, value, false); + } + + /** Constructor for potentially creating resize markers. */ + TableEntry(final long key, final V value, final boolean resize) { + this.key = key; + this.resizeMarker = resize; + this.setValuePlain(value); // Initial plain set + } + + public long getKey() { return this.key; } + public V getValue() { return getValueVolatile(); } + + public V setValue(V newValue) { + throw new UnsupportedOperationException("Direct setValue on TableEntry is not supported; use map methods."); + } + + @SuppressWarnings("unchecked") final V getValuePlain() { return (V) VALUE_HANDLE.get(this); } + @SuppressWarnings("unchecked") final V getValueAcquire() { return (V) VALUE_HANDLE.getAcquire(this); } + @SuppressWarnings("unchecked") final V getValueVolatile() { return (V) VALUE_HANDLE.getVolatile(this); } + + final void setValuePlain(final V value) { VALUE_HANDLE.set(this, value); } + final void setValueRelease(final V value) { VALUE_HANDLE.setRelease(this, value); } + final void setValueVolatile(final V value) { VALUE_HANDLE.setVolatile(this, value); } + + @SuppressWarnings("unchecked") + final boolean compareAndSetValueVolatile(final V expect, final V update) { + return VALUE_HANDLE.compareAndSet(this, expect, update); + } + + @SuppressWarnings("unchecked") final TableEntry getNextPlain() { return (TableEntry) NEXT_HANDLE.get(this); } + @SuppressWarnings("unchecked") final TableEntry getNextVolatile() { return (TableEntry) NEXT_HANDLE.getVolatile(this); } + + final void setNextPlain(final TableEntry next) { NEXT_HANDLE.set(this, next); } + final void setNextRelease(final TableEntry next) { NEXT_HANDLE.setRelease(this, next); } + final void setNextVolatile(final TableEntry next) { NEXT_HANDLE.setVolatile(this, next); } + + final boolean isResizeMarker() { return this.resizeMarker; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || !(o instanceof LeafConcurrentLong2ReferenceChainedHashTable.TableEntry)) return false; + TableEntry that = (TableEntry) o; + return key == that.key && Objects.equals(getValueVolatile(), that.getValueVolatile()); // Use volatile read for value + } + + @Override + public int hashCode() { + return Long.hashCode(key) ^ Objects.hashCode(getValueVolatile()); // Use volatile read for value + } + + @Override + public String toString() { + return key + "=" + getValueVolatile(); // Use volatile read for value + } + } + + /** + * Base class for traversing nodes, handling resizes. + * Note: This iterator implementation is simplified and might not be fully robust against + * rapid concurrent modifications during iteration, particularly multiple resize events. + * It aims for basic correctness in common scenarios. + */ + protected static class NodeIterator { + final LeafConcurrentLong2ReferenceChainedHashTable map; + TableEntry[] currentTable; + TableEntry nextNode; + int nextTableIndex; + TableEntry currentNodeInChain; // Current node within the chain being processed + + NodeIterator(TableEntry[] initialTable, LeafConcurrentLong2ReferenceChainedHashTable map) { + this.map = map; + this.currentTable = initialTable; // Start with the table state at iterator creation + this.nextNode = null; + // Start iteration from the end of the table backwards + this.nextTableIndex = (initialTable == null || initialTable.length == 0) ? -1 : initialTable.length - 1; + this.currentNodeInChain = null; + advance(); // Find the first element + } + + /** + * Advances to find the next valid node (non-null value, non-marker). + * Sets {@code nextNode}. Handles basic traversal and checks for table changes. + */ + final void advance() { + nextNode = null; // Assume no next node initially + + if (currentNodeInChain != null) { + currentNodeInChain = currentNodeInChain.getNextVolatile(); // Move to next in chain + } + + while (nextNode == null) { + if (currentNodeInChain != null) { + // Check if the node is valid (not marker, has value) + if (!currentNodeInChain.isResizeMarker() && currentNodeInChain.getValueVolatile() != null) { + nextNode = currentNodeInChain; // Found a valid node + return; // Exit advance + } + // Node invalid (marker or placeholder), move to the next + currentNodeInChain = currentNodeInChain.getNextVolatile(); + continue; // Check next node in chain + } + + if (nextTableIndex < 0) { + // Check if the underlying table reference changed (indicates resize) + // This is a simplified check; robust iterators might need more complex resize handling + if (this.currentTable != map.table) { + // Table changed, restart iteration from the new table + this.currentTable = map.table; + this.nextTableIndex = (this.currentTable == null || this.currentTable.length == 0) ? -1 : this.currentTable.length - 1; + this.currentNodeInChain = null; + // Retry finding a node from the beginning of the new table + continue; + } + // No table change and all bins checked + return; // Exhausted + } + + if (this.currentTable != null && this.nextTableIndex < this.currentTable.length) { + TableEntry head = getAtIndexVolatile(this.currentTable, this.nextTableIndex--); // Read head and decrement index + + if (head != null && !head.isResizeMarker()) { + // Start traversing this new chain + currentNodeInChain = head; + // Check if the head itself is a valid node + if (currentNodeInChain.getValueVolatile() != null) { + nextNode = currentNodeInChain; + return; // Found valid node (head of bin) + } + // Head is placeholder, continue loop to check next in chain + continue; + } + // Bin was empty or head was marker. Reset chain traversal. + currentNodeInChain = null; + } else { + // Table became null or index out of bounds (shouldn't happen unless table shrinks drastically) + // Force moving to next index to avoid infinite loop + nextTableIndex--; + currentNodeInChain = null; + // Consider checking map.table again here for robustness + if (this.currentTable != map.table) { + // Restart if table changed + this.currentTable = map.table; + this.nextTableIndex = (this.currentTable == null || this.currentTable.length == 0) ? -1 : this.currentTable.length - 1; + continue; + } + } + } // End while (nextNode == null) + } + + + public final boolean hasNext() { + return this.nextNode != null; + } + + /** Internal method to get the next node and advance. */ + final TableEntry findNext() { + TableEntry e = this.nextNode; + if (e == null) { + return null; // Signifies end for internal use + } + advance(); // Prepare for the *next* call + return e; // Return the previously found node + } + } + + /** + * Base class for concrete iterators (Entry, Key, Value). + * Handles remove() and NoSuchElementException. + */ + protected static abstract class BaseIteratorImpl extends NodeIterator implements Iterator { + protected TableEntry lastReturned; // Node returned by last next() call + + protected BaseIteratorImpl(final LeafConcurrentLong2ReferenceChainedHashTable map) { + super(map.table, map); // Initialize NodeIterator + this.lastReturned = null; + } + + /** Gets the next node, updates lastReturned, advances iterator. */ + protected final TableEntry nextNode() throws NoSuchElementException { + TableEntry node = this.nextNode; // Node pre-fetched by advance() + if (node == null) { + throw new NoSuchElementException(); + } + this.lastReturned = node; // Store for remove() + advance(); // Find the *next* node for the subsequent call + return node; // Return the current node + } + + @Override + public void remove() { + TableEntry last = this.lastReturned; + if (last == null) { + throw new IllegalStateException("next() not called or remove() already called"); + } + this.map.remove(last.key); // Delegate removal to map's method + this.lastReturned = null; // Prevent double remove + } + + @Override + public abstract T next() throws NoSuchElementException; // Must be implemented by subclass + + @Override + public void forEachRemaining(final Consumer action) { + Validate.notNull(action, "Action may not be null"); + while (hasNext()) { + action.accept(next()); + } + } + } + + /** Iterator over map entries (TableEntry objects). */ + protected static final class EntryIterator extends BaseIteratorImpl> { + EntryIterator(final LeafConcurrentLong2ReferenceChainedHashTable map) { super(map); } + + @Override public TableEntry next() throws NoSuchElementException { + return nextNode(); + } + } + + /** Iterator over map keys (long primitives). */ + protected static final class KeyIterator extends BaseIteratorImpl implements PrimitiveIterator.OfLong { + KeyIterator(final LeafConcurrentLong2ReferenceChainedHashTable map) { super(map); } + + @Override public long nextLong() throws NoSuchElementException { + return nextNode().key; + } + + @Override public Long next() throws NoSuchElementException { + return nextLong(); // Autoboxing + } + + @Override public void forEachRemaining(final LongConsumer action) { + Validate.notNull(action, "Action may not be null"); + while (hasNext()) { + action.accept(nextLong()); + } + } + + @Override public void forEachRemaining(final Consumer action) { + if (action instanceof LongConsumer) { + forEachRemaining((LongConsumer) action); + } else { + Validate.notNull(action, "Action may not be null"); + while (hasNext()) { + action.accept(nextLong()); // Autoboxing + } + } + } + } + + /** Iterator over map values. */ + protected static final class ValueIterator extends BaseIteratorImpl { + ValueIterator(final LeafConcurrentLong2ReferenceChainedHashTable map) { super(map); } + + @Override public V next() throws NoSuchElementException { + return nextNode().getValueVolatile(); // Volatile read for value + } + } + + // --- Collection Views --- + + /** Base class for Collection views (Values, EntrySet). */ + protected static abstract class BaseCollection implements Collection { + protected final LeafConcurrentLong2ReferenceChainedHashTable map; + + protected BaseCollection(LeafConcurrentLong2ReferenceChainedHashTable map) { + this.map = Validate.notNull(map); + } + + @Override public int size() { return map.size(); } + @Override public boolean isEmpty() { return map.isEmpty(); } + @Override public abstract boolean contains(Object o); // Subclass responsibility + + @Override public boolean containsAll(Collection c) { + Validate.notNull(c); + for (Object e : c) { + if (!contains(e)) return false; + } + return true; + } + + @Override public Object[] toArray() { + List list = new ArrayList<>(map.size()); + for (E e : this) list.add(e); // Uses iterator() from subclass + return list.toArray(); + } + + @Override @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + Validate.notNull(a); + List list = new ArrayList<>(map.size()); + for (E e : this) list.add(e); + return list.toArray(a); + } + + @Override public void clear() { map.clear(); } + @Override public boolean add(E e) { throw new UnsupportedOperationException(); } + @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } + + @Override public boolean remove(Object o) { + Iterator it = iterator(); // Subclass provides iterator + while (it.hasNext()) { + if (Objects.equals(o, it.next())) { + it.remove(); // Use iterator's safe remove + return true; + } + } + return false; + } + + @Override public boolean removeAll(Collection c) { + Validate.notNull(c); + boolean modified = false; + Iterator it = iterator(); + while (it.hasNext()) { + if (c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + @Override public boolean retainAll(Collection c) { + Validate.notNull(c); + boolean modified = false; + Iterator it = iterator(); + while (it.hasNext()) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + @Override public boolean removeIf(Predicate filter) { + Validate.notNull(filter); + boolean removed = false; + Iterator it = iterator(); + while (it.hasNext()) { + if (filter.test(it.next())) { + it.remove(); + removed = true; + } + } + return removed; + } + + @Override public String toString() { + Iterator it = iterator(); + if (! it.hasNext()) return "[]"; + StringBuilder sb = new StringBuilder("["); + for (;;) { + E e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (! it.hasNext()) return sb.append(']').toString(); + sb.append(',').append(' '); + } + } + + @Override public void forEach(Consumer action) { + Validate.notNull(action); + for (E e : this) { // Uses iterator() from subclass + action.accept(e); + } + } + } + + /** Collection view for the map's values. */ + protected static final class Values extends BaseCollection { + Values(LeafConcurrentLong2ReferenceChainedHashTable map) { super(map); } + + @Override public boolean contains(Object o) { + try { + return o != null && map.containsValue((V)o); + } catch (ClassCastException cce) { return false; } + } + + @Override public Iterator iterator() { return map.valueIterator(); } + } + + /** Set view for the map's entries (TableEntry objects). */ + protected static final class EntrySet extends BaseCollection> implements Set> { + EntrySet(LeafConcurrentLong2ReferenceChainedHashTable map) { super(map); } + + @Override public boolean contains(Object o) { + if (!(o instanceof LeafConcurrentLong2ReferenceChainedHashTable.TableEntry)) return false; + TableEntry entry = (TableEntry) o; + V mappedValue = map.get(entry.getKey()); // Concurrent read + // Use volatile read on entry's value for consistent comparison + return mappedValue != null && Objects.equals(mappedValue, entry.getValueVolatile()); + } + + @Override public Iterator> iterator() { return map.entryIterator(); } + + @Override public boolean remove(Object o) { + if (!(o instanceof LeafConcurrentLong2ReferenceChainedHashTable.TableEntry)) return false; + TableEntry entry = (TableEntry) o; + try { + // Use map's atomic remove(key, value) + // Use volatile read for the expected value + return map.remove(entry.getKey(), (V)entry.getValueVolatile()); + } catch(ClassCastException | NullPointerException cce) { // Handle potential type/null issues + return false; + } + } + + @Override public int hashCode() { + int h = 0; + for (TableEntry e : this) { + h += e.hashCode(); // Uses entry's hashCode + } + return h; + } + + @Override public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof Set)) return false; + Set c = (Set) o; + if (c.size() != size()) return false; + try { + // relies on containsAll checking entry equality correctly + return containsAll(c); + } catch (ClassCastException | NullPointerException unused) { + return false; + } + } + } +} From 28e2111198d7c3c84dee7b551cad77dde43643a8 Mon Sep 17 00:00:00 2001 From: Taiyou06 Date: Mon, 21 Apr 2025 23:36:24 +0200 Subject: [PATCH 09/45] will visit ChunkHolderManager again at some point --- ...imize-ThreadedTicketLevelPropagator.patch} | 0 .../0156-Reworked-ChunkHolderManager.patch | 151 ------------------ ...EffectUtil-getDigSpeedAmplification.patch} | 0 ...patch => 0158-Optimise-chunkUnloads.patch} | 0 4 files changed, 151 deletions(-) rename leaf-server/minecraft-patches/features/{0157-Optimize-ThreadedTicketLevelPropagator.patch => 0156-Optimize-ThreadedTicketLevelPropagator.patch} (100%) delete mode 100644 leaf-server/minecraft-patches/features/0156-Reworked-ChunkHolderManager.patch rename leaf-server/minecraft-patches/features/{0158-Optimise-MobEffectUtil-getDigSpeedAmplification.patch => 0157-Optimise-MobEffectUtil-getDigSpeedAmplification.patch} (100%) rename leaf-server/minecraft-patches/features/{0159-Optimise-chunkUnloads.patch => 0158-Optimise-chunkUnloads.patch} (100%) diff --git a/leaf-server/minecraft-patches/features/0157-Optimize-ThreadedTicketLevelPropagator.patch b/leaf-server/minecraft-patches/features/0156-Optimize-ThreadedTicketLevelPropagator.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0157-Optimize-ThreadedTicketLevelPropagator.patch rename to leaf-server/minecraft-patches/features/0156-Optimize-ThreadedTicketLevelPropagator.patch diff --git a/leaf-server/minecraft-patches/features/0156-Reworked-ChunkHolderManager.patch b/leaf-server/minecraft-patches/features/0156-Reworked-ChunkHolderManager.patch deleted file mode 100644 index a2e6aac8..00000000 --- a/leaf-server/minecraft-patches/features/0156-Reworked-ChunkHolderManager.patch +++ /dev/null @@ -1,151 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Mon, 14 Apr 2025 03:02:42 +0200 -Subject: [PATCH] Reworked ChunkHolderManager - - -diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -index a1f328a5c4ccc030c99762a68008ab1ecebdc06e..3de8d0fb485e55f3fc38a65c251f109335595468 100644 ---- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -+++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -@@ -349,12 +349,13 @@ public final class ChunkHolderManager { - @Override - protected void processLevelUpdates(final Long2ByteLinkedOpenHashMap updates) { - // first the necessary chunkholders must be created, so just update the ticket levels -+ final LeafConcurrentLong2ReferenceChainedHashTable holderMap = ChunkHolderManager.this.chunkHolders; - for (final Iterator iterator = updates.long2ByteEntrySet().fastIterator(); iterator.hasNext();) { - final Long2ByteMap.Entry entry = iterator.next(); - final long key = entry.getLongKey(); - final int newLevel = convertBetweenTicketLevels((int)entry.getByteValue()); - -- NewChunkHolder current = ChunkHolderManager.this.chunkHolders.get(key); -+ NewChunkHolder current = holderMap.get(key); - if (current == null && newLevel > MAX_TICKET_LEVEL) { - // not loaded and it shouldn't be loaded! - iterator.remove(); -@@ -371,7 +372,7 @@ public final class ChunkHolderManager { - if (current == null) { - // must create - current = ChunkHolderManager.this.createChunkHolder(key); -- ChunkHolderManager.this.chunkHolders.put(key, current); -+ holderMap.put(key, current); - current.updateTicketLevel(newLevel); - } else { - current.updateTicketLevel(newLevel); -@@ -737,20 +738,23 @@ public final class ChunkHolderManager { - - final int sectionShift = ((ChunkSystemServerLevel)this.world).moonrise$getRegionChunkShift(); - -+ - final Predicate> expireNow = (final Ticket ticket) -> { - long removeDelay = ((ChunkSystemTicket)(Object)ticket).moonrise$getRemoveDelay(); - if (removeDelay == NO_TIMEOUT_MARKER) { - return false; - } - --removeDelay; -- ((ChunkSystemTicket)(Object)ticket).moonrise$setRemoveDelay(removeDelay); -- return removeDelay <= 0L; -+ final long nextDelay = removeDelay - 1; -+ ((ChunkSystemTicket)(Object)ticket).moonrise$setRemoveDelay(nextDelay); -+ return nextDelay <= 0L; - }; - - for (final PrimitiveIterator.OfLong iterator = this.sectionToChunkToExpireCount.keyIterator(); iterator.hasNext();) { - final long sectionKey = iterator.nextLong(); - - if (!this.sectionToChunkToExpireCount.containsKey(sectionKey)) { -+ - // removed concurrently - continue; - } -@@ -773,37 +777,62 @@ public final class ChunkHolderManager { - final long chunkKey = entry.getLongKey(); - final int expireCount = entry.getIntValue(); - -+ - final SortedArraySet> tickets = this.tickets.get(chunkKey); -- final int levelBefore = getTicketLevelAt(tickets); -+ if (tickets == null) { -+ iterator1.remove(); -+ continue; -+ } - -+ final int levelBefore; -+ final Ticket firstBefore; - final int sizeBefore = tickets.size(); -- tickets.removeIf(expireNow); -- final int sizeAfter = tickets.size(); -- final int levelAfter = getTicketLevelAt(tickets); - -- if (tickets.isEmpty()) { -- this.tickets.remove(chunkKey); -- } -- if (levelBefore != levelAfter) { -- this.updateTicketLevel(chunkKey, levelAfter); -+ if (!tickets.isEmpty()) { -+ firstBefore = tickets.first(); -+ levelBefore = firstBefore.getTicketLevel(); -+ } else { -+ firstBefore = null; -+ levelBefore = MAX_TICKET_LEVEL + 1; - } - -- final int newExpireCount = expireCount - (sizeBefore - sizeAfter); -+ final boolean changed = tickets.removeIf(expireNow); - -- if (newExpireCount == expireCount) { -- continue; -- } -+ if (changed) { -+ final int sizeAfter = tickets.size(); -+ final int levelAfter; -+ boolean levelMightHaveChanged = true; - -- if (newExpireCount != 0) { -- entry.setValue(newExpireCount); -- } else { -- iterator1.remove(); -+ if (tickets.isEmpty()) { -+ levelAfter = MAX_TICKET_LEVEL + 1; -+ this.tickets.remove(chunkKey); -+ } else { -+ final Ticket firstAfter = tickets.first(); -+ if (firstBefore == firstAfter) { -+ levelMightHaveChanged = false; -+ levelAfter = levelBefore; -+ } else { -+ levelAfter = firstAfter.getTicketLevel(); -+ } -+ } -+ -+ if (levelMightHaveChanged && levelBefore != levelAfter) { -+ this.updateTicketLevel(chunkKey, levelAfter); -+ } -+ -+ final int removedCount = sizeBefore - sizeAfter; -+ if (removedCount > 0) { -+ final int newExpireCount = expireCount - removedCount; -+ if (newExpireCount > 0) { -+ entry.setValue(newExpireCount); -+ } else { -+ iterator1.remove(); -+ } -+ } - } -- } - -- if (chunkToExpireCount.isEmpty()) { -- this.sectionToChunkToExpireCount.remove(sectionKey); - } -+ if (chunkToExpireCount.isEmpty()) { this.sectionToChunkToExpireCount.remove(sectionKey); } - } finally { - this.ticketLockArea.unlock(ticketLock); - } -@@ -812,6 +841,7 @@ public final class ChunkHolderManager { - this.processTicketUpdates(); - } - -+ - public NewChunkHolder getChunkHolder(final int chunkX, final int chunkZ) { - return this.chunkHolders.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); - } diff --git a/leaf-server/minecraft-patches/features/0158-Optimise-MobEffectUtil-getDigSpeedAmplification.patch b/leaf-server/minecraft-patches/features/0157-Optimise-MobEffectUtil-getDigSpeedAmplification.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0158-Optimise-MobEffectUtil-getDigSpeedAmplification.patch rename to leaf-server/minecraft-patches/features/0157-Optimise-MobEffectUtil-getDigSpeedAmplification.patch diff --git a/leaf-server/minecraft-patches/features/0159-Optimise-chunkUnloads.patch b/leaf-server/minecraft-patches/features/0158-Optimise-chunkUnloads.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0159-Optimise-chunkUnloads.patch rename to leaf-server/minecraft-patches/features/0158-Optimise-chunkUnloads.patch From ef1559c7813bc9f2c3fec2b6a08617b77a64f522 Mon Sep 17 00:00:00 2001 From: John Smith <41772578+huzpsb@users.noreply.github.com> Date: Tue, 22 Apr 2025 12:06:23 +0800 Subject: [PATCH 10/45] Fix flush frequency in linear region flush logic (#293) Fixed an issue where linear always flushes --- .../linearpaper/region/LinearRegionFile.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/leaf-server/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java b/leaf-server/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java index 428e507e..9bdbed24 100644 --- a/leaf-server/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java +++ b/leaf-server/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java @@ -33,6 +33,8 @@ import net.minecraft.world.level.ChunkPos; import org.dreeam.leaf.config.modules.misc.RegionFormatConfig; import org.slf4j.Logger; +import net.minecraft.server.MinecraftServer; + public class LinearRegionFile implements IRegionFile { private static final long SUPERBLOCK = -4323716122432332390L; @@ -144,6 +146,11 @@ public class LinearRegionFile implements IRegionFile { } private synchronized void save() throws IOException { + if (MinecraftServer.getServer().hasStopped()) { + // Crazy - save only once on shutdown + if (!closed) return; + } + long timestamp = getTimestamp(); short chunkCount = 0; @@ -217,7 +224,8 @@ public class LinearRegionFile implements IRegionFile { LOGGER.error("Chunk write IOException {} {}", e, this.path); } - if ((System.nanoTime() - this.lastFlushed) >= TimeUnit.NANOSECONDS.toSeconds(RegionFormatConfig.linearFlushFrequency)) { + // Crazy: bruh + if (TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - this.lastFlushed) >= (RegionFormatConfig.linearFlushFrequency)) { this.flushWrapper(); } } From 9db6bfba3b85a4b0b0ecfeca6b281a089732f8aa Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Tue, 22 Apr 2025 23:55:01 +0800 Subject: [PATCH 11/45] improve Async target finding (#294) * fix NearestHealableRaiderTargetGoal not async and fix data race in Async target finding * remove unnecessary requiresUpdateEveryTick * add AsyncGoal * use cow list on ServerLevel#players * move warning * run tasks after entity tick --- .../features/0155-Async-target-finding.patch | 433 ++++++++++++------ .../org/dreeam/leaf/async/ai/AsyncGoal.java | 5 + .../leaf/async/ai/AsyncGoalExecutor.java | 19 + 3 files changed, 329 insertions(+), 128 deletions(-) create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoal.java create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java diff --git a/leaf-server/minecraft-patches/features/0155-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0155-Async-target-finding.patch index e03e8c9f..fc413a26 100644 --- a/leaf-server/minecraft-patches/features/0155-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0155-Async-target-finding.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Async target finding diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 24926aa7ed5c78b235659daf18b224b14beb744c..64d5cebd488892c93f07b938ce8dc3e99fddcdad 100644 +index 24926aa7ed5c78b235659daf18b224b14beb744c..53716dcdb9d3409b7bc71f3064be42bc3f81a86b 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -1088,6 +1088,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop players = Lists.newArrayList(); ++ // Leaf start - Async target finding ++ final List players; ++ { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ this.players = Lists.newCopyOnWriteArrayList(); ++ } else { ++ this.players = Lists.newArrayList(); ++ } ++ } ++ // Leaf end - Async target finding + public final ServerChunkCache chunkSource; + private final MinecraftServer server; + public final net.minecraft.world.level.storage.PrimaryLevelData serverLevelData; // CraftBukkit - type +@@ -218,6 +227,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) + public boolean hasRidableMoveEvent = false; // Purpur - Ridables + final List realPlayers; // Leaves - skip ++ public List asyncAITasks = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); // Leaf + + public LevelChunk getChunkIfLoaded(int x, int z) { + return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately +@@ -861,6 +871,15 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + ); + this.tickBlockEntities(); ++ // Leaf start - Async target finding ++ final List tasks = this.asyncAITasks; ++ this.asyncAITasks = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.EXECUTOR.execute(() -> { ++ for (Runnable asyncTask : tasks) { ++ asyncTask.run(); ++ } ++ }); ++ // Leaf end - Async target finding + } + + // Paper - rewrite chunk system diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java index 075fcbcde23b5bb7b27ff622e8d188c3a2583973..e5aa7c542b1e9c9362aa1feeeebef45919feac70 100644 --- a/net/minecraft/world/entity/Entity.java @@ -72,11 +118,105 @@ index 075fcbcde23b5bb7b27ff622e8d188c3a2583973..e5aa7c542b1e9c9362aa1feeeebef459 } // Paper start - Folia schedulers +diff --git a/net/minecraft/world/entity/ai/goal/GoalSelector.java b/net/minecraft/world/entity/ai/goal/GoalSelector.java +index e82e32407cec6109b9c3b0106295217f4a3f4aa2..d7361d2f0a777f745133c3687637ba6ab451af5b 100644 +--- a/net/minecraft/world/entity/ai/goal/GoalSelector.java ++++ b/net/minecraft/world/entity/ai/goal/GoalSelector.java +@@ -22,17 +22,30 @@ public class GoalSelector { + }; + private final Map lockedFlags = new EnumMap<>(Goal.Flag.class); + private final Set availableGoals = new ObjectLinkedOpenHashSet<>(); ++ private final java.util.List asyncGoals = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); // Leaf + private static final Goal.Flag[] GOAL_FLAG_VALUES = Goal.Flag.values(); // Paper - remove streams from GoalSelector + private final ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet goalTypes = new ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from GoalSelector + private int curRate; // Paper - EAR 2 + + public void addGoal(int priority, Goal goal) { +- this.availableGoals.add(new WrappedGoal(priority, goal)); ++ // Leaf start ++ WrappedGoal wrapped = new WrappedGoal(priority, goal); ++ if (this.availableGoals.add(wrapped) ++ && org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled ++ && goal instanceof org.dreeam.leaf.async.ai.AsyncGoal) { ++ asyncGoals.add(wrapped); ++ } ++ // Leaf end + } + + @VisibleForTesting + public void removeAllGoals(Predicate filter) { + this.availableGoals.removeIf(wrappedGoal -> filter.test(wrappedGoal.getGoal())); ++ // Leaf start ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ this.asyncGoals.removeIf(wrappedGoal -> filter.test(wrappedGoal.getGoal())); ++ } ++ // Leaf end + } + + // Paper start - EAR 2 +@@ -41,8 +54,37 @@ public class GoalSelector { + tickRate = Math.min(tickRate, 3); // Dreeam TODO - Waiting Paper + this.curRate++; + //return this.curRate % 3 == 0; // TODO newGoalRate was already unused in 1.20.4, check if this is correct +- return this.curRate % tickRate == 0; ++ // Leaf start ++ boolean willTick = this.curRate % tickRate == 0; ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ for (var wrappedGoal : this.asyncGoals) { ++ if (wrappedGoal.getGoal() instanceof org.dreeam.leaf.async.ai.AsyncGoal asyncGoal ++ && asyncGoal.poll() ++ && !willTick) { ++ if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.goalTypes) || !wrappedGoal.canContinueToUse())) { ++ wrappedGoal.stop(); ++ } ++ if (!wrappedGoal.isRunning() && !goalContainsAnyFlags(wrappedGoal, this.goalTypes) && goalCanBeReplacedForAllFlags(wrappedGoal, this.lockedFlags) && wrappedGoal.canUse()) { ++ long flagIterator = wrappedGoal.getFlags().getBackingSet(); ++ int wrappedGoalSize = wrappedGoal.getFlags().size(); ++ for (int i = 0; i < wrappedGoalSize; ++i) { ++ final Goal.Flag flag = GOAL_FLAG_VALUES[Long.numberOfTrailingZeros(flagIterator)]; ++ flagIterator ^= ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit(flagIterator); ++ // Paper end ++ WrappedGoal wrappedGoal1 = this.lockedFlags.getOrDefault(flag, NO_GOAL); ++ wrappedGoal1.stop(); ++ this.lockedFlags.put(flag, wrappedGoal); ++ } ++ ++ wrappedGoal.start(); ++ // wrappedGoal.tick(); ++ } ++ } ++ } ++ } ++ return willTick; + // Pufferfish end ++ // Leaf end + } + + public boolean hasTasks() { +@@ -63,6 +105,11 @@ public class GoalSelector { + } + + this.availableGoals.removeIf(wrappedGoal1 -> wrappedGoal1.getGoal() == goal); ++ // Leaf start ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ this.asyncGoals.removeIf(wrappedGoal1 -> wrappedGoal1.getGoal() == goal); ++ } ++ // Leaf end + } + + // Paper start - Perf: optimize goal types diff --git a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -index 41ee3cdc45ecc8376a2203ed588bb544ed377294..1320d4af037dd0af7b8003372a53df81d008fd3b 100644 +index 41ee3cdc45ecc8376a2203ed588bb544ed377294..49e836a012f0db9dec6578caa341a50e703ee98e 100644 --- a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -@@ -17,7 +17,21 @@ public class NearestAttackableTargetGoal extends TargetG +@@ -11,13 +11,20 @@ import net.minecraft.world.entity.ai.targeting.TargetingConditions; + import net.minecraft.world.entity.player.Player; + import net.minecraft.world.phys.AABB; + +-public class NearestAttackableTargetGoal extends TargetGoal { ++public class NearestAttackableTargetGoal extends TargetGoal implements org.dreeam.leaf.async.ai.AsyncGoal { // Leaf + private static final int DEFAULT_RANDOM_INTERVAL = 10; + protected final Class targetType; protected final int randomInterval; @Nullable protected LivingEntity target; @@ -84,33 +224,17 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..1320d4af037dd0af7b8003372a53df81 + @Nullable protected TargetingConditions.Selector selector; // Leaf - create TargetingConditions instead of reusing it + // Leaf start - Async Target Finding + private static final org.apache.logging.log4j.Logger LOGGER = org.apache.logging.log4j.LogManager.getLogger("Leaf Async Target Lookup"); -+ // Single thread executor to prevent overwhelming the server -+ public static final java.util.concurrent.ExecutorService TARGET_EXECUTOR = java.util.concurrent.Executors.newSingleThreadExecutor( -+ new com.google.common.util.concurrent.ThreadFactoryBuilder() -+ .setNameFormat("Leaf Async Target Finding Thread") -+ .setDaemon(true) -+ .setPriority(Thread.NORM_PRIORITY - 2) -+ .build()); -+ + // Flag to track if a search is in progress + private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); + private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); ++ protected boolean readyNow = false; + // Leaf end - Async Target Finding public NearestAttackableTargetGoal(Mob mob, Class targetType, boolean mustSee) { this(mob, targetType, 10, mustSee, false, null); -@@ -36,19 +50,45 @@ public class NearestAttackableTargetGoal extends TargetG - ) { - super(mob, mustSee, mustReach); +@@ -38,11 +45,17 @@ public class NearestAttackableTargetGoal extends TargetG this.targetType = targetType; -- this.randomInterval = reducedTickDelay(interval); -+ // Leaf start - update every tick -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ this.randomInterval = interval; -+ } else { -+ this.randomInterval = reducedTickDelay(interval); -+ } -+ // Leaf end - update every tick + this.randomInterval = reducedTickDelay(interval); this.setFlags(EnumSet.of(Goal.Flag.TARGET)); - this.targetConditions = TargetingConditions.forCombat().range(this.getFollowDistance()).selector(selector); + this.selector = selector; // Leaf @@ -119,44 +243,60 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..1320d4af037dd0af7b8003372a53df81 @Override public boolean canUse() { + // Leaf start - Async target finding -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ LivingEntity t = pendingTarget.getAcquire(); -+ if (t != null) { -+ pendingTarget.setRelease(null); -+ ServerLevel serverLevel = getServerLevel(this.mob); -+ if (serverLevel != null && t.isAlive() && this.getTargetConditions().test(serverLevel, this.mob, t)) { -+ this.target = t; -+ } -+ return true; -+ } ++ if (readyNow) { ++ readyNow = false; ++ return true; + } ++ // Leaf end - Async target finding if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) { return false; -+ } else if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ this.findTargetAsync(); -+ ServerLevel serverLevel = getServerLevel(this.mob); -+ if (this.target != null && !(serverLevel != null && this.target.isAlive() && this.getTargetConditions().test(serverLevel, this.mob, this.target))) { -+ this.target = null; -+ } -+ return false; } else { - this.findTarget(); - return this.target != null; - } -+ // Leaf end - Async target finding +@@ -55,8 +68,37 @@ public class NearestAttackableTargetGoal extends TargetG + return this.mob.getBoundingBox().inflate(targetDistance, targetDistance, targetDistance); } - protected AABB getTargetSearchArea(double targetDistance) { -@@ -71,6 +111,176 @@ public class NearestAttackableTargetGoal extends TargetG - } - } - -+ // Leaf start - find target async -+ protected void findTargetAsync() { -+ if (isSearching.getAcquire()) { ++ // Leaf start - Async target finding ++ @Override ++ public boolean poll() { ++ LivingEntity t = pendingTarget.getAndSet(null); ++ if (t == null) { ++ return false; ++ } ++ ServerLevel serverLevel = getServerLevel(this.mob); ++ if (serverLevel == null || !t.isAlive() || !this.getTargetConditions().test(serverLevel, this.mob, t)) { ++ return false; ++ } ++ this.target = t; ++ if (this.readyNow) { ++ LOGGER.warn("NearestAttackableTargetGoal#findTarget call twice"); ++ } ++ this.readyNow = true; ++ return true; ++ } ++ + protected void findTarget() { + ServerLevel serverLevel = getServerLevel(this.mob); ++ ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ this.findTargetAsync(); ++ if (serverLevel == null || this.target == null || !this.target.isAlive() || !this.getTargetConditions().test(serverLevel, this.mob, this.target)) { ++ this.target = null; ++ return; ++ } ++ return; ++ } ++ + if (this.targetType != Player.class && this.targetType != ServerPlayer.class) { + this.target = serverLevel.getNearestEntity( + this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true), +@@ -71,6 +113,156 @@ public class NearestAttackableTargetGoal extends TargetG + } + } + ++ private void findTargetAsync() { ++ if (!isSearching.compareAndSet(false, true)) { + return; + } -+ isSearching.setRelease(true); + + // Capture mutable state to avoid race conditions + final Mob mob = this.mob; @@ -175,40 +315,36 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..1320d4af037dd0af7b8003372a53df81 + final Class targetType = this.targetType; + final double maxDistSqr = followDistance * followDistance; + final AABB targetSearch = getTargetSearchArea(this.getFollowDistance()); ++ final ServerLevel serverLevel = getServerLevel(mob); + -+ TARGET_EXECUTOR.execute(() -> { ++ serverLevel.asyncAITasks.add(() -> { + try { -+ ServerLevel serverLevel = getServerLevel(mob); -+ if (serverLevel == null) { ++ if (mob.level() == null || mob.level() != serverLevel) { + return; + } + if (mob.isRemoved() || !mob.isAlive()) { + return; + } + -+ try { -+ if (targetType != Player.class && targetType != ServerPlayer.class) { -+ java.util.List entities; -+ try { -+ entities = mob.level().getEntitiesOfClass(targetType, targetSearch, entity -> entity != null && entity != mob && !entity.isRemoved() && entity.isAlive()); -+ } catch (Exception e) { -+ LOGGER.warn("Error getting entities", e); -+ return; -+ } ++ if (targetType != Player.class && targetType != ServerPlayer.class) { ++ java.util.List entities; ++ try { ++ entities = mob.level().getEntitiesOfClass(targetType, targetSearch, entity -> entity != null && entity != mob && !entity.isRemoved() && entity.isAlive()); ++ } catch (Exception e) { ++ LOGGER.warn("Exception getting entities", e); ++ return; ++ } + -+ if (entities != null && !entities.isEmpty()) { -+ var result = findNearestEntitySafely(entities, targetConditions, mob, x, y, z, serverLevel, maxDistSqr); -+ pendingTarget.setRelease(result); -+ } -+ } else { -+ var result = findNearestPlayerSafely(targetConditions, mob, x, y, z, serverLevel); ++ if (entities != null && !entities.isEmpty()) { ++ var result = findNearestEntitySafely(entities, targetConditions, mob, x, y, z, serverLevel, maxDistSqr); + pendingTarget.setRelease(result); + } -+ } catch (Exception e) { -+ LOGGER.warn("Error finding entities in async target finder", e); ++ } else { ++ var result = findNearestPlayerSafely(targetConditions, mob, x, y, z, serverLevel); ++ pendingTarget.setRelease(result); + } + } catch (Exception e) { -+ LOGGER.warn("Error during async target finding", e); ++ LOGGER.warn("Exception during async target finding", e); + } finally { + isSearching.setRelease(false); + } @@ -216,7 +352,7 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..1320d4af037dd0af7b8003372a53df81 + } + + @Nullable -+ private LivingEntity findNearestEntitySafely( ++ private static LivingEntity findNearestEntitySafely( + java.util.List entities, + TargetingConditions conditions, + Mob source, @@ -234,40 +370,33 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..1320d4af037dd0af7b8003372a53df81 + double closestDistSq = maxDistSqr; + LivingEntity closest = null; + -+ for (int i = 0; i < entities.size(); i++) { -+ try { -+ LivingEntity entity = entities.get(i); -+ if (entity == null || entity == source || entity.isRemoved() || !entity.isAlive()) { -+ continue; -+ } ++ for (LivingEntity entity : entities) { ++ if (entity == null || entity == source || entity.isRemoved() || !entity.isAlive()) { ++ continue; ++ } + -+ if (conditions.test(level, source, entity)) { -+ double dx = entity.getX() - x; -+ double dy = entity.getY() - y; -+ double dz = entity.getZ() - z; -+ double distSq = dx * dx + dy * dy + dz * dz; ++ if (conditions.test(level, source, entity)) { ++ double dx = entity.getX() - x; ++ double dy = entity.getY() - y; ++ double dz = entity.getZ() - z; ++ double distSq = dx * dx + dy * dy + dz * dz; + -+ if (distSq < closestDistSq) { -+ closestDistSq = distSq; -+ closest = entity; -+ } ++ if (distSq < closestDistSq) { ++ closestDistSq = distSq; ++ closest = entity; + } -+ } catch (IndexOutOfBoundsException e) { -+ break; -+ } catch (Exception e) { -+ LOGGER.warn("Error processing entity in findNearestEntitySafely", e); + } + } + + return closest; + } catch (Exception e) { -+ LOGGER.warn("Error in findNearestEntitySafely", e); ++ LOGGER.error("Exception in findNearestEntitySafely", e); + return null; + } + } + + @Nullable -+ private Player findNearestPlayerSafely( ++ private static Player findNearestPlayerSafely( + TargetingConditions conditions, + Mob source, + double x, @@ -288,43 +417,36 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..1320d4af037dd0af7b8003372a53df81 + double closestDistSq = -1.0; + Player closest = null; + -+ for (int i = 0; i < players.size(); i++) { -+ try { -+ Player player = players.get(i); -+ if (player == null || player.isRemoved() || !player.isAlive()) { -+ continue; -+ } ++ for (Player player : players) { ++ if (player == null || player.isRemoved() || !player.isAlive()) { ++ continue; ++ } + -+ if (conditions.test(level, source, player)) { -+ double dx = player.getX() - x; -+ double dy = player.getY() - y; -+ double dz = player.getZ() - z; -+ double distSq = dx * dx + dy * dy + dz * dz; ++ if (conditions.test(level, source, player)) { ++ double dx = player.getX() - x; ++ double dy = player.getY() - y; ++ double dz = player.getZ() - z; ++ double distSq = dx * dx + dy * dy + dz * dz; + -+ if (closestDistSq == -1.0 || distSq < closestDistSq) { -+ closestDistSq = distSq; -+ closest = player; -+ } ++ if (closestDistSq == -1.0 || distSq < closestDistSq) { ++ closestDistSq = distSq; ++ closest = player; + } -+ } catch (IndexOutOfBoundsException e) { -+ break; -+ } catch (Exception e) { -+ System.err.println("Error processing player in findNearestPlayerSafely: " + e.getMessage()); + } + } + + return closest; + } catch (Exception e) { -+ System.err.println("Error in findNearestPlayerSafely: " + e.getMessage()); ++ LOGGER.error("Exception in findNearestPlayerSafely", e); + return null; + } + } -+ // Leaf end - find target async ++ // Leaf end - Async target finding + @Override public void start() { this.mob.setTarget(this.target, this.target instanceof ServerPlayer ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true); // CraftBukkit - reason -@@ -81,7 +291,16 @@ public class NearestAttackableTargetGoal extends TargetG +@@ -81,7 +273,9 @@ public class NearestAttackableTargetGoal extends TargetG this.target = target; } @@ -333,16 +455,39 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..1320d4af037dd0af7b8003372a53df81 + // Leaf start + protected TargetingConditions getTargetConditions() { + return TargetingConditions.forCombat().range(this.getFollowDistance()).selector(this.selector); -+ } -+ // Leaf end -+ -+ // Leaf start - update every tick -+ @Override -+ public boolean requiresUpdateEveryTick() { -+ return org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled; } -+ // Leaf end - update every tick ++ // Leaf end } +diff --git a/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java +index 4604a603c4ddd0a9242e859aaa5a511c2d4c4f84..af9101bb0639dedee41e2e3e97e05cad91ec42e7 100644 +--- a/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java ++++ b/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java +@@ -10,7 +10,7 @@ public class NearestHealableRaiderTargetGoal extends Nea + private int cooldown = 0; + + public NearestHealableRaiderTargetGoal(Raider raider, Class targetType, boolean mustSee, @Nullable TargetingConditions.Selector selector) { +- super(raider, targetType, 500, mustSee, false, selector); ++ super(raider, targetType, 100, mustSee, false, selector); // Leaf 500 -> 100 seem doesn't used before + } + + public int getCooldown() { +@@ -23,6 +23,16 @@ public class NearestHealableRaiderTargetGoal extends Nea + + @Override + public boolean canUse() { ++ // Leaf start - Async target finding ++ if (readyNow) { ++ readyNow = false; ++ return ((Raider) this.mob).hasActiveRaid(); ++ } ++ // 5 sec ++ if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) { ++ return false; ++ } ++ // Leaf end - Async target finding + if (this.cooldown > 0 || !this.mob.getRandom().nextBoolean()) { + return false; + } else if (!((Raider)this.mob).hasActiveRaid()) { diff --git a/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java index abf57494950f55bbd75f335f26736cb9e703c197..683722fbd07fcae9cdd72928ed25fd084202b57e 100644 --- a/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java @@ -412,7 +557,7 @@ index 002d3c0d8b1107a275020d5c582c37e9a5c536ee..3f8c18f4040f4929df79ba85906330b1 // Gale start - Petal - reduce line of sight cache lookups - merge sets int cached = this.seen.get(id); diff --git a/net/minecraft/world/entity/animal/Fox.java b/net/minecraft/world/entity/animal/Fox.java -index 90452f0945e761077608692877677f522d38bccd..736eea50b4460041242bdd4b7191d67d7dd4524d 100644 +index 90452f0945e761077608692877677f522d38bccd..28dbba5494779d82b6ae7c435db0ed76dc5eaf1f 100644 --- a/net/minecraft/world/entity/animal/Fox.java +++ b/net/minecraft/world/entity/animal/Fox.java @@ -849,13 +849,18 @@ public class Fox extends Animal implements VariantHolder { @@ -435,3 +580,35 @@ index 90452f0945e761077608692877677f522d38bccd..736eea50b4460041242bdd4b7191d67d } } +@@ -863,6 +868,13 @@ public class Fox extends Animal implements VariantHolder { + } + } + ++ // Leaf start - Async target finding ++ @Override ++ public boolean poll() { ++ return false; ++ } ++ // Leaf end - Async target finding ++ + @Override + public void start() { + this.setTarget(this.trustedLastHurtBy); +diff --git a/net/minecraft/world/entity/monster/EnderMan.java b/net/minecraft/world/entity/monster/EnderMan.java +index c7897532163d4fdf5a82982f7d24a47dd61e3dfa..914d171c0183f00ff474517e2fa3ec00d7f1d6f1 100644 +--- a/net/minecraft/world/entity/monster/EnderMan.java ++++ b/net/minecraft/world/entity/monster/EnderMan.java +@@ -608,6 +608,13 @@ public class EnderMan extends Monster implements NeutralMob { + this.enderman.setBeingStaredAt(); + } + ++ // Leaf start - Async target finding ++ @Override ++ public boolean poll() { ++ return false; ++ } ++ // Leaf end - Async target finding ++ + @Override + public void stop() { + this.pendingTarget = null; diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoal.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoal.java new file mode 100644 index 00000000..41651921 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoal.java @@ -0,0 +1,5 @@ +package org.dreeam.leaf.async.ai; + +public interface AsyncGoal { + boolean poll(); +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java new file mode 100644 index 00000000..df7feb76 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java @@ -0,0 +1,19 @@ +package org.dreeam.leaf.async.ai; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class AsyncGoalExecutor { + public static final java.util.concurrent.ExecutorService EXECUTOR = new ThreadPoolExecutor( + 1, + 1, + 0L, + TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(128), + new com.google.common.util.concurrent.ThreadFactoryBuilder() + .setNameFormat("Leaf Async Target Finding Thread") + .setDaemon(true) + .setPriority(Thread.NORM_PRIORITY - 2) + .build(), new ThreadPoolExecutor.CallerRunsPolicy()); +} From 7e30f9be891fc4c826d6badd2ec5f8214f6f7ee0 Mon Sep 17 00:00:00 2001 From: Dreeam-qwq <61569423+Dreeam-qwq@users.noreply.github.com> Date: Wed, 23 Apr 2025 01:40:54 +0000 Subject: [PATCH 12/45] Updated Upstream (Gale) Upstream has released updates that appear to apply and compile correctly Gale Changes: Dreeam-qwq/Gale@f57773d2 Fix diff --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 387a6685..0815ac26 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ group=cn.dreeam.leaf mcVersion=1.21.4 version=1.21.4-R0.1-SNAPSHOT -galeCommit=3c153f5f8e7b4f4ae122163bd2ec238bab532e89 +galeCommit=f57773d224f54c451a9862b2ea22d5634f4078a4 org.gradle.configuration-cache=true org.gradle.caching=true From f8578ef8b1b7488457402fb3d0f99a20e053fbd9 Mon Sep 17 00:00:00 2001 From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> Date: Thu, 24 Apr 2025 03:26:29 +1400 Subject: [PATCH 13/45] Improve PWT config handling --- .../async/SparklyPaperParallelWorldTicking.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java index 7eaea021..f668a123 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java @@ -2,6 +2,7 @@ package org.dreeam.leaf.config.modules.async; import org.dreeam.leaf.config.ConfigModules; import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.LeafConfig; import org.dreeam.leaf.config.annotations.Experimental; public class SparklyPaperParallelWorldTicking extends ConfigModules { @@ -25,16 +26,24 @@ public class SparklyPaperParallelWorldTicking extends ConfigModules { Enables parallel world ticking to improve performance on multi-core systems..""", """ **实验性功能** - 启用并行世界处理以提高多核系统的性能."""); + 启用并行世界处理以提高多核 CPU 使用率."""); enabled = config.getBoolean(getBasePath() + ".enabled", enabled); threads = config.getInt(getBasePath() + ".threads", threads); - threads = enabled ? threads : 0; + if (enabled) { + if (threads <= 0) threads = 8; + } else { + threads = 0; + } logContainerCreationStacktraces = config.getBoolean(getBasePath() + ".log-container-creation-stacktraces", logContainerCreationStacktraces); logContainerCreationStacktraces = enabled && logContainerCreationStacktraces; disableHardThrow = config.getBoolean(getBasePath() + ".disable-hard-throw", disableHardThrow); disableHardThrow = enabled && disableHardThrow; runAsyncTasksSync = config.getBoolean(getBasePath() + ".run-async-tasks-sync", runAsyncTasksSync); runAsyncTasksSync = enabled && runAsyncTasksSync; + + if (enabled) { + LeafConfig.LOGGER.info("Using {} threads for Parallel World Ticking", threads); + } } } From 124dc64a0daae8ed40a967549ddce15800feaedb Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Thu, 24 Apr 2025 18:18:53 +0800 Subject: [PATCH 14/45] update async target finding and block finding (#296) * reduce overhead on poll * more async search entities * async block search * rename search entity config * cleanup * fix async search block too frequent * remove alertOther Experimental anno * Adjust the delay of RemoveBlockGoal to match vanilla behavior * Optimize TemptGoal * rollback interval change * cleanup * add async finding to DefendVillageTargetGoal * rollback interval change for NearestHealableRaiderTargetGoal * config searchPlayer * fix DefendVillageTargetGoal condition doesn't check * add async finding to BegGoal * rollback interval change for FollowMobGoal * cleanup * add async finding to some follow goal * add async finding to TemptGoal * handle searchPlayer config * fix TemptGoal --- .../features/0148-Async-Block-Finding.patch | 183 -- ...se-direct-iteration-on-Sensing.tick.patch} | 0 ...9-Optimise-non-flush-packet-sending.patch} | 0 ...unk-retrieving-in-entity-fluid-push.patch} | 0 ...-Null-handling-on-MultifaceSpreader.patch} | 0 ....patch => 0152-More-virtual-threads.patch} | 0 ...tLong2ReferenceChainedHashTable-wit.patch} | 0 .../features/0154-Async-target-finding.patch | 2060 +++++++++++++++++ .../features/0155-Async-target-finding.patch | 614 ----- ...imize-ThreadedTicketLevelPropagator.patch} | 0 ...EffectUtil-getDigSpeedAmplification.patch} | 0 ...patch => 0157-Optimise-chunkUnloads.patch} | 0 .../org/dreeam/leaf/async/ai/AsyncGoal.java | 5 - .../leaf/async/ai/AsyncGoalExecutor.java | 27 +- .../modules/async/AsyncBlockFinding.java | 28 - .../modules/async/AsyncTargetFinding.java | 42 +- 16 files changed, 2112 insertions(+), 847 deletions(-) delete mode 100644 leaf-server/minecraft-patches/features/0148-Async-Block-Finding.patch rename leaf-server/minecraft-patches/features/{0149-Use-direct-iteration-on-Sensing.tick.patch => 0148-Use-direct-iteration-on-Sensing.tick.patch} (100%) rename leaf-server/minecraft-patches/features/{0150-Optimise-non-flush-packet-sending.patch => 0149-Optimise-non-flush-packet-sending.patch} (100%) rename leaf-server/minecraft-patches/features/{0151-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch => 0150-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch} (100%) rename leaf-server/minecraft-patches/features/{0152-Null-handling-on-MultifaceSpreader.patch => 0151-Null-handling-on-MultifaceSpreader.patch} (100%) rename leaf-server/minecraft-patches/features/{0153-More-virtual-threads.patch => 0152-More-virtual-threads.patch} (100%) rename leaf-server/minecraft-patches/features/{0154-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch => 0153-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch} (100%) create mode 100644 leaf-server/minecraft-patches/features/0154-Async-target-finding.patch delete mode 100644 leaf-server/minecraft-patches/features/0155-Async-target-finding.patch rename leaf-server/minecraft-patches/features/{0156-Optimize-ThreadedTicketLevelPropagator.patch => 0155-Optimize-ThreadedTicketLevelPropagator.patch} (100%) rename leaf-server/minecraft-patches/features/{0157-Optimise-MobEffectUtil-getDigSpeedAmplification.patch => 0156-Optimise-MobEffectUtil-getDigSpeedAmplification.patch} (100%) rename leaf-server/minecraft-patches/features/{0158-Optimise-chunkUnloads.patch => 0157-Optimise-chunkUnloads.patch} (100%) delete mode 100644 leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoal.java delete mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncBlockFinding.java diff --git a/leaf-server/minecraft-patches/features/0148-Async-Block-Finding.patch b/leaf-server/minecraft-patches/features/0148-Async-Block-Finding.patch deleted file mode 100644 index c527e135..00000000 --- a/leaf-server/minecraft-patches/features/0148-Async-Block-Finding.patch +++ /dev/null @@ -1,183 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Sun, 23 Mar 2025 11:51:44 +0100 -Subject: [PATCH] Async Block Finding - - -diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..d70ed3ace6fa8f97bcc0d493842f44f43072a610 100644 ---- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -+++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -@@ -20,6 +20,18 @@ public abstract class MoveToBlockGoal extends Goal { - private final int verticalSearchRange; - protected int verticalSearchStart; - -+ // Leaf start - Async Block Finding -+ private static final java.util.concurrent.ExecutorService BLOCK_FINDER_EXECUTOR = -+ java.util.concurrent.Executors.newSingleThreadExecutor( -+ new com.google.common.util.concurrent.ThreadFactoryBuilder() -+ .setNameFormat("Leaf Block Finding Thread - %d") -+ .setDaemon(true) -+ .build()); -+ -+ private final java.util.concurrent.ConcurrentLinkedQueue candidateBlocks = new java.util.concurrent.ConcurrentLinkedQueue<>(); -+ private boolean asyncSearchInProgress = false; -+ // Leaf end - Async Block Finding -+ - public MoveToBlockGoal(PathfinderMob mob, double speedModifier, int searchRange) { - this(mob, speedModifier, searchRange, 1); - } -@@ -29,6 +41,10 @@ public abstract class MoveToBlockGoal extends Goal { - super.stop(); - this.blockPos = BlockPos.ZERO; - this.mob.movingTarget = null; -+ // Leaf start - Async Block Finding - Reset async state on goal stop -+ this.candidateBlocks.clear(); -+ this.asyncSearchInProgress = false; -+ // Leaf end - Async Block Finding - Reset async state on goal stop - } - // Paper end - -@@ -53,23 +69,23 @@ public abstract class MoveToBlockGoal extends Goal { - } - - protected int nextStartTick(PathfinderMob creature) { -- return reducedTickDelay(200 + creature.getRandom().nextInt(200)); -+ return Goal.reducedTickDelay(200 + creature.getRandom().nextInt(200)); // Leaf - Async Block Finding - Use the static method from the Goal class directly - } - - @Override - public boolean canContinueToUse() { -- return this.tryTicks >= -this.maxStayTicks && this.tryTicks <= 1200 && this.isValidTarget(this.mob.level(), this.blockPos); -+ return this.tryTicks >= -this.maxStayTicks && this.tryTicks <= 1200 && this.blockPos != BlockPos.ZERO && this.isValidTarget(this.mob.level(), this.blockPos); // Leaf - Async Block Finding - } - - @Override - public void start() { -- this.moveMobToBlock(); -+ if (this.blockPos != BlockPos.ZERO) this.moveMobToBlock(); // Leaf - Async Block Finding - this.tryTicks = 0; - this.maxStayTicks = this.mob.getRandom().nextInt(this.mob.getRandom().nextInt(1200) + 1200) + 1200; - } - - protected void moveMobToBlock() { -- this.mob.getNavigation().moveTo(this.blockPos.getX() + 0.5, this.blockPos.getY() + 1, this.blockPos.getZ() + 0.5, this.speedModifier); -+ if (this.blockPos != BlockPos.ZERO) this.mob.getNavigation().moveTo(this.blockPos.getX() + 0.5, this.blockPos.getY() + 1, this.blockPos.getZ() + 0.5, this.speedModifier); // Leaf - Async Block Finding - } - - public double acceptedDistance() { -@@ -77,7 +93,7 @@ public abstract class MoveToBlockGoal extends Goal { - } - - protected BlockPos getMoveToTarget() { -- return this.blockPos.above(); -+ return this.blockPos != BlockPos.ZERO ? this.blockPos.above() : BlockPos.ZERO; // Leaf - Async Block Finding - } - - @Override -@@ -87,7 +103,10 @@ public abstract class MoveToBlockGoal extends Goal { - - @Override - public void tick() { -+ if (this.blockPos == BlockPos.ZERO) return; // Leaf - Async Block Finding - BlockPos moveToTarget = this.getMoveToTarget(); -+ if (moveToTarget == BlockPos.ZERO) return; // Leaf - Async Block Finding -+ - if (!moveToTarget.closerToCenterThan(this.mob.position(), this.acceptedDistance())) { - this.reachedTarget = false; - this.tryTicks++; -@@ -109,20 +128,90 @@ public abstract class MoveToBlockGoal extends Goal { - } - - protected boolean findNearestBlock() { -+ // Leaf start - Async Block Finding -+ if (!org.dreeam.leaf.config.modules.async.AsyncBlockFinding.enabled) { -+ return findNearestBlockSync(); -+ } -+ -+ while (!candidateBlocks.isEmpty()) { -+ BlockPos pos = candidateBlocks.poll(); -+ if (pos != null && this.mob.level().hasChunkAt(pos) && -+ this.mob.isWithinRestriction(pos) && -+ this.isValidTarget(this.mob.level(), pos)) { -+ -+ this.blockPos = pos; -+ this.mob.movingTarget = this.blockPos == BlockPos.ZERO ? null : this.blockPos; // Use the assigned blockPos -+ return true; -+ } -+ } -+ -+ if (asyncSearchInProgress) { -+ return false; -+ } -+ -+ // Check again before starting, avoids tiny race condition if canUse is called rapidly -+ if (!asyncSearchInProgress) { -+ asyncSearchInProgress = true; -+ final BlockPos centerPos = this.mob.blockPosition().immutable(); -+ final int searchRange = this.searchRange; -+ final int verticalRange = this.verticalSearchRange; -+ final int verticalStart = this.verticalSearchStart; -+ -+ BLOCK_FINDER_EXECUTOR.execute(() -> { -+ try { -+ generateCandidateBlocks(centerPos, searchRange, verticalRange, verticalStart); -+ } catch (Exception e) { -+ e.printStackTrace(); // Keep basic error logging -+ } finally { -+ asyncSearchInProgress = false; -+ } -+ }); -+ } -+ -+ return false; -+ } -+ -+ private void generateCandidateBlocks(BlockPos center, int searchRange, int verticalRange, int verticalStart) { -+ java.util.List positions = new java.util.ArrayList<>(); -+ -+ for (int i2 = verticalStart; i2 <= verticalRange; i2 = i2 > 0 ? -i2 : 1 - i2) { -+ for (int i3 = 0; i3 < searchRange; i3++) { -+ for (int i4 = 0; i4 <= i3; i4 = i4 > 0 ? -i4 : 1 - i4) { -+ for (int i5 = i4 < i3 && i4 > -i3 ? i3 : 0; i5 <= i3; i5 = i5 > 0 ? -i5 : 1 - i5) { -+ BlockPos pos = center.offset(i4, i2 - 1, i5); -+ positions.add(pos.immutable()); -+ } -+ } -+ } -+ } -+ -+ positions.sort((p1, p2) -> { -+ double d1 = p1.distSqr(center); -+ double d2 = p2.distSqr(center); -+ return Double.compare(d1, d2); -+ }); -+ -+ for (BlockPos pos : positions) { -+ candidateBlocks.add(pos); -+ } -+ } -+ -+ protected boolean findNearestBlockSync() { -+ // Leaf end - Async Block Finding - int i = this.searchRange; - int i1 = this.verticalSearchRange; -- BlockPos blockPos = this.mob.blockPosition(); -+ BlockPos blockPosOrigin = this.mob.blockPosition(); // Leaf - Async Block Finding - BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); - - for (int i2 = this.verticalSearchStart; i2 <= i1; i2 = i2 > 0 ? -i2 : 1 - i2) { - for (int i3 = 0; i3 < i; i3++) { - for (int i4 = 0; i4 <= i3; i4 = i4 > 0 ? -i4 : 1 - i4) { - for (int i5 = i4 < i3 && i4 > -i3 ? i3 : 0; i5 <= i3; i5 = i5 > 0 ? -i5 : 1 - i5) { -- mutableBlockPos.setWithOffset(blockPos, i4, i2 - 1, i5); -+ mutableBlockPos.setWithOffset(blockPosOrigin, i4, i2 - 1, i5); // Leaf - Async Block Finding - if (!this.mob.level().hasChunkAt(mutableBlockPos)) continue; // Gale - Airplane - block goal does not load chunks - if this block isn't loaded, continue - if (this.mob.isWithinRestriction(mutableBlockPos) && this.isValidTarget(this.mob.level(), mutableBlockPos)) { -- this.blockPos = mutableBlockPos; -- this.mob.movingTarget = mutableBlockPos == BlockPos.ZERO ? null : mutableBlockPos.immutable(); // Paper -+ this.blockPos = mutableBlockPos.immutable(); // Leaf - Async Block Finding -+ this.mob.movingTarget = this.blockPos == BlockPos.ZERO ? null : this.blockPos; // Paper // Leaf - Async Block Finding - Use the assigned blockPos - return true; - } - } diff --git a/leaf-server/minecraft-patches/features/0149-Use-direct-iteration-on-Sensing.tick.patch b/leaf-server/minecraft-patches/features/0148-Use-direct-iteration-on-Sensing.tick.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0149-Use-direct-iteration-on-Sensing.tick.patch rename to leaf-server/minecraft-patches/features/0148-Use-direct-iteration-on-Sensing.tick.patch diff --git a/leaf-server/minecraft-patches/features/0150-Optimise-non-flush-packet-sending.patch b/leaf-server/minecraft-patches/features/0149-Optimise-non-flush-packet-sending.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0150-Optimise-non-flush-packet-sending.patch rename to leaf-server/minecraft-patches/features/0149-Optimise-non-flush-packet-sending.patch diff --git a/leaf-server/minecraft-patches/features/0151-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch b/leaf-server/minecraft-patches/features/0150-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0151-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch rename to leaf-server/minecraft-patches/features/0150-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch diff --git a/leaf-server/minecraft-patches/features/0152-Null-handling-on-MultifaceSpreader.patch b/leaf-server/minecraft-patches/features/0151-Null-handling-on-MultifaceSpreader.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0152-Null-handling-on-MultifaceSpreader.patch rename to leaf-server/minecraft-patches/features/0151-Null-handling-on-MultifaceSpreader.patch diff --git a/leaf-server/minecraft-patches/features/0153-More-virtual-threads.patch b/leaf-server/minecraft-patches/features/0152-More-virtual-threads.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0153-More-virtual-threads.patch rename to leaf-server/minecraft-patches/features/0152-More-virtual-threads.patch diff --git a/leaf-server/minecraft-patches/features/0154-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch b/leaf-server/minecraft-patches/features/0153-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0154-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch rename to leaf-server/minecraft-patches/features/0153-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch diff --git a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch new file mode 100644 index 00000000..7b8c239d --- /dev/null +++ b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch @@ -0,0 +1,2060 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sat, 29 Mar 2025 13:40:46 +0100 +Subject: [PATCH] Async target finding + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 24926aa7ed5c78b235659daf18b224b14beb744c..2603f5ca5e5f3fd86af76aec7e16039bf9c9292d 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1088,6 +1088,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop players = Lists.newArrayList(); ++ // Leaf start - Async target finding ++ final List players; ++ { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ this.players = Lists.newCopyOnWriteArrayList(); ++ } else { ++ this.players = Lists.newArrayList(); ++ } ++ } ++ // Leaf end - Async target finding + public final ServerChunkCache chunkSource; + private final MinecraftServer server; + public final net.minecraft.world.level.storage.PrimaryLevelData serverLevelData; // CraftBukkit - type +@@ -218,6 +227,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) + public boolean hasRidableMoveEvent = false; // Purpur - Ridables + final List realPlayers; // Leaves - skip ++ public List asyncAITasks = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); // Leaf + + public LevelChunk getChunkIfLoaded(int x, int z) { + return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately +@@ -861,6 +871,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + ); + this.tickBlockEntities(); ++ // Leaf start - Async target finding ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ final var tasks = this.asyncAITasks; ++ this.asyncAITasks = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.EXECUTOR.execute( ++ () -> org.dreeam.leaf.async.ai.AsyncGoalExecutor.runTasks(tasks)); ++ } ++ // Leaf end - Async target finding + } + + // Paper - rewrite chunk system +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index 075fcbcde23b5bb7b27ff622e8d188c3a2583973..e5aa7c542b1e9c9362aa1feeeebef45919feac70 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -243,6 +243,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + protected Vec3 stuckSpeedMultiplier = Vec3.ZERO; + @Nullable + private Entity.RemovalReason removalReason; ++ private final java.util.concurrent.atomic.AtomicBoolean isRemoved = new java.util.concurrent.atomic.AtomicBoolean(false); // Leaf - atomic removal check + public static final float DEFAULT_BB_WIDTH = 0.6F; + public static final float DEFAULT_BB_HEIGHT = 1.8F; + public float moveDist; +@@ -5028,7 +5029,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + public final boolean isRemoved() { +- return this.removalReason != null; ++ // Leaf start - atomic removal check ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ return this.isRemoved.getAcquire(); ++ } else { ++ return this.removalReason != null; ++ } ++ // Leaf end - atomic removal check + } + + @Nullable +@@ -5055,6 +5062,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers + if (this.removalReason == null) { + this.removalReason = removalReason; ++ // Leaf start - atomic removal check ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ this.isRemoved.setRelease(true); ++ } ++ // Leaf end - atomic removal check + } + + if (this.removalReason.shouldDestroy()) { +@@ -5074,6 +5086,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + + public void unsetRemoved() { + this.removalReason = null; ++ // Leaf start - atomic removal check ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ this.isRemoved.setRelease(false); ++ } ++ // Leaf end - atomic removal check + } + + // Paper start - Folia schedulers +diff --git a/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java b/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java +index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..77e51ffce5afc9089fd3a2e382cdfde7a6cf1c35 100644 +--- a/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java ++++ b/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java +@@ -65,17 +65,32 @@ public class AvoidEntityGoal extends Goal { + this(mob, entityClassToAvoid, livingEntity -> true, maxDistance, walkSpeedModifier, sprintSpeedModifier, predicateOnAvoidEntity); + } + ++ // Leaf start - Async Avoid Entity Finding ++ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); ++ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); ++ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; ++ // Leaf end - Async Avoid Entity Finding ++ + @Override + public boolean canUse() { +- this.toAvoid = getServerLevel(this.mob) +- .getNearestEntity( +- this.mob.level().getEntitiesOfClass(this.avoidClass, this.mob.getBoundingBox().inflate(this.maxDist, 3.0, this.maxDist), livingEntity -> true), +- this.avoidEntityTargeting, +- this.mob, +- this.mob.getX(), +- this.mob.getY(), +- this.mob.getZ() +- ); ++ // Leaf start - Async Avoid Entity Finding ++ if (poll()) { ++ return true; ++ } ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ getNearestEntityAsync(); ++ } else { ++ this.toAvoid = getServerLevel(this.mob) ++ .getNearestEntity( ++ this.mob.level().getEntitiesOfClass(this.avoidClass, this.mob.getBoundingBox().inflate(this.maxDist, 3.0, this.maxDist), livingEntity -> true), ++ this.avoidEntityTargeting, ++ this.mob, ++ this.mob.getX(), ++ this.mob.getEyeY(), ++ this.mob.getZ() ++ ); ++ } ++ // Leaf end - Async Avoid Entity Finding + if (this.toAvoid == null) { + return false; + } else { +@@ -91,6 +106,62 @@ public class AvoidEntityGoal extends Goal { + } + } + ++ // Leaf start - Async Avoid Entity Finding ++ private boolean poll() { ++ LivingEntity t = pendingTarget.getAndSet(null); ++ if (t == null) { ++ return false; ++ } ++ var serverLevel = getServerLevel(this.mob); ++ if (serverLevel == null || !t.isAlive() || !this.avoidEntityTargeting.test(serverLevel, this.mob, t)) { ++ return false; ++ } ++ this.toAvoid = (T) t; ++ return true; ++ } ++ ++ private void getNearestEntityAsync() { ++ if (!isSearching.compareAndSet(false, true)) { ++ return; ++ } ++ ++ final var mob = this.mob; ++ ++ if (mob == null || mob.isRemoved() || !mob.isAlive()) { ++ isSearching.setRelease(false); ++ return; ++ } ++ ++ final double x = mob.getX(); ++ final double y = mob.getEyeY(); ++ final double z = mob.getZ(); ++ final var serverLevel = getServerLevel(mob); ++ final var avoidClass = this.avoidClass; ++ final var bound = mob.getBoundingBox().inflate(this.maxDist, 3.0, this.maxDist); ++ final var pendingTarget = this.pendingTarget; ++ serverLevel.asyncAITasks.add(() -> { ++ try { ++ if (mob == null || mob.isRemoved() || !mob.isAlive() || mob.level() != serverLevel) { ++ return; ++ } ++ var avoid = serverLevel.getNearestEntity( ++ serverLevel.getEntitiesOfClass(avoidClass, bound, LivingEntity::isAlive), ++ avoidEntityTargeting, ++ mob, ++ x, ++ y, ++ z ++ ); ++ pendingTarget.setRelease(avoid); ++ } catch (Exception e) { ++ LOGGER.error("Exception getting entities", e); ++ } finally { ++ isSearching.setRelease(false); ++ } ++ }); ++ } ++ // Leaf end - Async Avoid Entity Finding ++ + @Override + public boolean canContinueToUse() { + return !this.pathNav.isDone(); +diff --git a/net/minecraft/world/entity/ai/goal/BegGoal.java b/net/minecraft/world/entity/ai/goal/BegGoal.java +index 28ef40e8a645989ea181297069cf2bbe571f3082..56a5fda5f1e2482e1c89c7c2f9c34ea85562702e 100644 +--- a/net/minecraft/world/entity/ai/goal/BegGoal.java ++++ b/net/minecraft/world/entity/ai/goal/BegGoal.java +@@ -27,8 +27,74 @@ public class BegGoal extends Goal { + this.setFlags(EnumSet.of(Goal.Flag.LOOK)); + } + ++ // Leaf start - Async Target Finding ++ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; ++ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); ++ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); ++ protected boolean poll() { ++ Player t = pendingTarget.getAndSet(null); ++ if (t == null) { ++ return false; ++ } ++ ServerLevel serverLevel = getServerLevel(this.wolf); ++ if (serverLevel == null || !t.isAlive() || !playerHoldingInteresting(t)) { ++ return false; ++ } ++ this.player = t; ++ return true; ++ } ++ ++ private void findTargetAsync() { ++ if (!isSearching.compareAndSet(false, true)) { ++ return; ++ } ++ ++ // Capture mutable state to avoid race conditions ++ final Wolf wolf = this.wolf; ++ ++ // Safety check ++ if (wolf == null || wolf.isRemoved() || !wolf.isAlive()) { ++ isSearching.setRelease(false); ++ return; ++ } ++ ++ final double x = wolf.getX(); ++ final double y = wolf.getEyeY(); ++ final double z = wolf.getZ(); ++ final ServerLevel serverLevel = getServerLevel(wolf); ++ final TargetingConditions begTargeting = this.begTargeting; ++ final var pendingTarget = this.pendingTarget; ++ ++ serverLevel.asyncAITasks.add(() -> { ++ try { ++ if (wolf.level() != serverLevel || wolf.isRemoved() || !wolf.isAlive()) { ++ return; ++ } ++ ++ var player = serverLevel.getNearestPlayer(begTargeting, wolf); ++ if (player != null && playerHoldingInteresting(player)) { ++ pendingTarget.setRelease(player); ++ } ++ } catch (Exception e) { ++ LOGGER.error("Exception getting player", e); ++ } finally { ++ isSearching.setRelease(false); ++ } ++ }); ++ } ++ // Leaf end - Async Target Finding ++ + @Override + public boolean canUse() { ++ // Leaf start - Async Target Finding ++ if (poll()) { ++ return true; ++ } ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchPlayer) { ++ findTargetAsync(); ++ return false; ++ } ++ // Leaf end - Async Target Finding + this.player = this.level.getNearestPlayer(this.begTargeting, this.wolf); + return this.player != null && this.playerHoldingInteresting(this.player); + } +@@ -59,14 +125,16 @@ public class BegGoal extends Goal { + this.lookTime--; + } + +- private boolean playerHoldingInteresting(Player player) { ++ // Leaf start - static ++ private static boolean playerHoldingInteresting(Player player) { + for (InteractionHand interactionHand : InteractionHand.values()) { + ItemStack itemInHand = player.getItemInHand(interactionHand); +- if (itemInHand.is(Items.BONE) || this.wolf.isFood(itemInHand)) { ++ if (itemInHand.is(Items.BONE) || itemInHand.is(net.minecraft.tags.ItemTags.WOLF_FOOD)) { + return true; + } + } + + return false; + } ++ // Leaf end - static + } +diff --git a/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java b/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java +index 4c46cd105cde3cbcde65a02ff691c3a8edd56c76..d26bebdc66bf30a30fba5c1ba70e71479aff68a1 100644 +--- a/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java ++++ b/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java +@@ -52,6 +52,14 @@ public class CatLieOnBedGoal extends MoveToBlockGoal { + + @Override + protected boolean isValidTarget(LevelReader level, BlockPos pos) { ++ // Leaf - Async search block + return level.isEmptyBlock(pos.above()) && level.getBlockState(pos).is(BlockTags.BEDS); + } ++ ++ // Leaf start - Async search block ++ @Override ++ protected TypeToCheck typeToCheck() { ++ return TypeToCheck.CatLie; ++ } ++ // Leaf end - Async search block + } +diff --git a/net/minecraft/world/entity/ai/goal/CatSitOnBlockGoal.java b/net/minecraft/world/entity/ai/goal/CatSitOnBlockGoal.java +index 9954f49bc364969c7ccb37f4186fa2ab8710f6ae..b0a93215a7d5e8fe58331f261d7b4f87852cd4b2 100644 +--- a/net/minecraft/world/entity/ai/goal/CatSitOnBlockGoal.java ++++ b/net/minecraft/world/entity/ai/goal/CatSitOnBlockGoal.java +@@ -44,9 +44,11 @@ public class CatSitOnBlockGoal extends MoveToBlockGoal { + + @Override + protected boolean isValidTarget(LevelReader level, BlockPos pos) { ++ // Leaf - Async search block + if (!level.isEmptyBlock(pos.above())) { + return false; + } else { ++ // Leaf - Async search block + BlockState blockState = level.getBlockState(pos); + return blockState.is(Blocks.CHEST) + ? ChestBlockEntity.getOpenCount(level, pos) < 1 +@@ -54,4 +56,11 @@ public class CatSitOnBlockGoal extends MoveToBlockGoal { + || blockState.is(BlockTags.BEDS, state -> state.getOptionalValue(BedBlock.PART).map(bedPart -> bedPart != BedPart.HEAD).orElse(true)); + } + } ++ ++ // Leaf start - Async search block ++ @Override ++ protected TypeToCheck typeToCheck() { ++ return TypeToCheck.CatSit; ++ } ++ // Leaf end - Async search block + } +diff --git a/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java b/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java +index 1fe4d1877ed52017d4d22ddb9b77f78e2b93dff9..1f478e26a743fe6fdeccbe2c1f0efed0387fcb1e 100644 +--- a/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java ++++ b/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java +@@ -23,8 +23,71 @@ public class FollowBoatGoal extends Goal { + this.mob = mob; + } + ++ // Leaf start - Async Target Finding ++ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; ++ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); ++ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); ++ ++ private boolean poll() { ++ Player t = pendingTarget.getAndSet(null); ++ if (t == null) { ++ return false; ++ } ++ var serverLevel = getServerLevel(this.mob); ++ return serverLevel != null && t.isAlive() && !t.isSpectator() && (Mth.abs(t.xxa) > 0.0F || Mth.abs(t.zza) > 0.0F); ++ } ++ ++ private void findTargetAsync() { ++ if (!isSearching.compareAndSet(false, true)) { ++ return; ++ } ++ ++ final PathfinderMob mob = this.mob; ++ if (mob == null || mob.isRemoved() || !mob.isAlive()) { ++ isSearching.setRelease(false); ++ return; ++ } ++ ++ final var bound = mob.getBoundingBox().inflate(5.0); ++ final var serverLevel = getServerLevel(mob); ++ final var pendingTarget = this.pendingTarget; ++ serverLevel.asyncAITasks.add(() -> { ++ try { ++ if (mob.level() != serverLevel || mob.isRemoved() || !mob.isAlive()) { ++ return; ++ } ++ ++ List entitiesOfClass = serverLevel.getEntitiesOfClass(AbstractBoat.class, bound); ++ for (AbstractBoat abstractBoat : entitiesOfClass) { ++ Entity controllingPassenger = abstractBoat.getControllingPassenger(); ++ if (controllingPassenger instanceof Player player ++ && (Mth.abs(player.xxa) > 0.0F || Mth.abs(player.zza) > 0.0F)) { ++ pendingTarget.setRelease(player); ++ break; ++ } ++ } ++ } catch (Exception e) { ++ LOGGER.error("Exception getting entities", e); ++ } finally { ++ isSearching.setRelease(false); ++ } ++ }); ++ } ++ // Leaf end - Async Target Finding + @Override + public boolean canUse() { ++ // Leaf start - Async Target Finding ++ if (poll()) { ++ return true; ++ } ++ if (this.following != null && (Mth.abs(this.following.xxa) > 0.0F || Mth.abs(this.following.zza) > 0.0F)) { ++ return true; ++ } ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ findTargetAsync(); ++ return false; ++ } ++ // Leaf end - Async Target Finding + List entitiesOfClass = this.mob.level().getEntitiesOfClass(AbstractBoat.class, this.mob.getBoundingBox().inflate(5.0)); + boolean flag = false; + +@@ -37,7 +100,7 @@ public class FollowBoatGoal extends Goal { + } + } + +- return this.following != null && (Mth.abs(this.following.xxa) > 0.0F || Mth.abs(this.following.zza) > 0.0F) || flag; ++ return flag; // Leaf - move above + } + + @Override +diff --git a/net/minecraft/world/entity/ai/goal/FollowMobGoal.java b/net/minecraft/world/entity/ai/goal/FollowMobGoal.java +index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..48708da53db05f2668b4e1eb52cd3effa49ea983 100644 +--- a/net/minecraft/world/entity/ai/goal/FollowMobGoal.java ++++ b/net/minecraft/world/entity/ai/goal/FollowMobGoal.java +@@ -36,8 +36,23 @@ public class FollowMobGoal extends Goal { + } + } + ++ // Leaf start - Async Follow Mob Finding ++ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); ++ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); ++ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; ++ // Leaf end - Async Follow Mob Finding ++ + @Override + public boolean canUse() { ++ // Leaf start - Async Follow Mob Finding ++ if (poll()) { ++ return true; ++ } ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ getFollowingMobAsync(); ++ return false; ++ } ++ // Leaf end - Async Follow Mob Finding + List entitiesOfClass = this.mob.level().getEntitiesOfClass(Mob.class, this.mob.getBoundingBox().inflate(this.areaSize), this.followPredicate); + if (!entitiesOfClass.isEmpty()) { + for (Mob mob : entitiesOfClass) { +@@ -51,6 +66,62 @@ public class FollowMobGoal extends Goal { + return false; + } + ++ // Leaf start - Async Follow Mob Finding ++ private boolean poll() { ++ var t = pendingTarget.getAndSet(null); ++ if (t == null) { ++ return false; ++ } ++ var serverLevel = getServerLevel(this.mob); ++ if (serverLevel == null || !t.isAlive() || t.isInvisible()) { ++ return false; ++ } ++ this.followingMob = t; ++ return true; ++ } ++ ++ private void getFollowingMobAsync() { ++ if (!isSearching.compareAndSet(false, true)) { ++ return; ++ } ++ ++ final var mob = this.mob; ++ ++ if (mob == null || mob.isRemoved() || !mob.isAlive()) { ++ isSearching.setRelease(false); ++ return; ++ } ++ ++ final double x = mob.getX(); ++ final double y = mob.getEyeY(); ++ final double z = mob.getZ(); ++ final var serverLevel = getServerLevel(mob); ++ final var bound = this.mob.getBoundingBox().inflate(this.areaSize); ++ final var followPredicate = this.followPredicate; ++ final var pendingTarget = this.pendingTarget; ++ serverLevel.asyncAITasks.add(() -> { ++ try { ++ if (mob == null || mob.isRemoved() || !mob.isAlive() || mob.level() != serverLevel) { ++ return; ++ } ++ List entitiesOfClass = serverLevel.getEntitiesOfClass(Mob.class, bound, followPredicate); ++ if (!entitiesOfClass.isEmpty()) { ++ for (final Mob follow : entitiesOfClass) { ++ if (!follow.isInvisible()) { ++ pendingTarget.setRelease(follow); ++ return; ++ } ++ } ++ } ++ } catch (Exception e) { ++ LOGGER.error("Exception getting entities", e); ++ } finally { ++ isSearching.setRelease(false); ++ } ++ }); ++ } ++ // Leaf end - Async Follow Mob Finding ++ + @Override + public boolean canContinueToUse() { + return this.followingMob != null && !this.navigation.isDone() && this.mob.distanceToSqr(this.followingMob) > this.stopDistance * this.stopDistance; +diff --git a/net/minecraft/world/entity/ai/goal/FollowParentGoal.java b/net/minecraft/world/entity/ai/goal/FollowParentGoal.java +index 3093f03d4f298bf39fec8bad2b6c22518774aea8..7d97345e8a5c630bf53cce3bd543e46e0b596237 100644 +--- a/net/minecraft/world/entity/ai/goal/FollowParentGoal.java ++++ b/net/minecraft/world/entity/ai/goal/FollowParentGoal.java +@@ -19,11 +19,84 @@ public class FollowParentGoal extends Goal { + this.speedModifier = speedModifier; + } + ++ // Leaf start - Async Target Finding ++ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; ++ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); ++ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); ++ protected boolean poll() { ++ var t = pendingTarget.getAndSet(null); ++ if (t == null) { ++ return false; ++ } ++ var serverLevel = getServerLevel(animal); ++ if (serverLevel == null || !t.isAlive() || animal.distanceToSqr(t) < 9.0) { ++ return false; ++ } ++ this.parent = animal; ++ return true; ++ } ++ ++ private void findTargetAsync() { ++ if (!isSearching.compareAndSet(false, true)) { ++ return; ++ } ++ ++ final Animal animal = this.animal; ++ if (animal == null || animal.isRemoved() || !animal.isAlive()) { ++ isSearching.setRelease(false); ++ return; ++ } ++ ++ final var targetType = animal.getClass(); ++ final var bound = animal.getBoundingBox().inflate(8.0, 4.0, 8.0); ++ final var serverLevel = getServerLevel(animal); ++ final var pos = animal.position(); ++ final var pendingTarget = this.pendingTarget; ++ serverLevel.asyncAITasks.add(() -> { ++ try { ++ if (animal.level() != serverLevel || animal.isRemoved() || !animal.isAlive()) { ++ return; ++ } ++ ++ List entitiesOfClass = serverLevel.getEntitiesOfClass(targetType, bound); ++ Animal target = null; ++ double d = Double.MAX_VALUE; ++ ++ for (Animal animal1 : entitiesOfClass) { ++ if (animal1.getAge() >= 0) { ++ double d1 = animal1.distanceToSqr(pos); ++ if (!(d1 > d)) { ++ d = d1; ++ target = animal1; ++ } ++ } ++ } ++ if (target != null) { ++ pendingTarget.setRelease(target); ++ } ++ } catch (Exception e) { ++ LOGGER.error("Exception getting entities", e); ++ } finally { ++ isSearching.setRelease(false); ++ } ++ }); ++ } ++ // Leaf end - Async Target Finding ++ + @Override + public boolean canUse() { + if (this.animal.getAge() >= 0) { + return false; + } else { ++ // Leaf start - Async Target Finding ++ if (poll()) { ++ return true; ++ } ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ findTargetAsync(); ++ return false; ++ } ++ // Leaf end - Async Target Finding + List entitiesOfClass = this.animal + .level() + .getEntitiesOfClass((Class)this.animal.getClass(), this.animal.getBoundingBox().inflate(8.0, 4.0, 8.0)); +@@ -43,6 +116,7 @@ public class FollowParentGoal extends Goal { + if (animal == null) { + return false; + } else if (d < 9.0) { ++ // Leaf - Async Target Finding + return false; + } else { + this.parent = animal; +diff --git a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java +index be59d0c27a83b329ec3f97c029cfb9c114e22472..23c5e933ccb98a38228810953a7e2447d5115c84 100644 +--- a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java ++++ b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java +@@ -20,19 +20,110 @@ public class LlamaFollowCaravanGoal extends Goal { + this.setFlags(EnumSet.of(Goal.Flag.MOVE)); + } + ++ // Leaf start - Async Target Finding ++ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; ++ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); ++ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); ++ private @javax.annotation.Nullable Llama poll() { ++ var t = pendingTarget.getAndSet(null); ++ if (t == null) { ++ return null; ++ } ++ var serverLevel = getServerLevel(this.llama); ++ if (serverLevel == null || !t.isAlive()) { ++ return null; ++ } ++ return t; ++ } ++ ++ private void findTargetAsync() { ++ if (!isSearching.compareAndSet(false, true)) { ++ return; ++ } ++ ++ final Llama llama = this.llama; ++ if (llama == null || llama.isRemoved() || !llama.isAlive()) { ++ isSearching.setRelease(false); ++ return; ++ } ++ ++ final var bound = llama.getBoundingBox().inflate(9.0, 4.0, 9.0); ++ final var serverLevel = getServerLevel(llama); ++ final var pos = llama.position(); ++ final var pendingTarget = this.pendingTarget; ++ serverLevel.asyncAITasks.add(() -> { ++ try { ++ if (llama.level() != serverLevel || llama.isRemoved() || !llama.isAlive()) { ++ return; ++ } ++ ++ List entities = serverLevel.getEntities(llama, bound, entity1 -> { ++ EntityType type = entity1.getType(); ++ return type == EntityType.LLAMA || type == EntityType.TRADER_LLAMA; ++ }); ++ Llama target = null; ++ double d = Double.MAX_VALUE; ++ ++ for (Entity entity : entities) { ++ Llama llama1 = (Llama)entity; ++ if (llama1.inCaravan() && !llama1.hasCaravanTail()) { ++ double d1 = llama1.distanceToSqr(pos); ++ if (!(d1 > d)) { ++ d = d1; ++ target = llama1; ++ } ++ } ++ } ++ ++ if (target == null) { ++ for (Entity entityx : entities) { ++ Llama llama1 = (Llama)entityx; ++ if (llama1.isLeashed() && !llama1.hasCaravanTail()) { ++ double d1 = llama1.distanceToSqr(pos); ++ if (!(d1 > d)) { ++ d = d1; ++ target = llama1; ++ } ++ } ++ } ++ } ++ if (target != null) { ++ pendingTarget.setRelease(target); ++ } ++ } catch (Exception e) { ++ LOGGER.error("Exception getting entities", e); ++ } finally { ++ isSearching.setRelease(false); ++ } ++ }); ++ } ++ // Leaf end - Async Target Finding + @Override + public boolean canUse() { + if (!this.llama.level().purpurConfig.llamaJoinCaravans || !this.llama.shouldJoinCaravan) return false; // Purpur - Llama API // Purpur - Config to disable Llama caravans + if (!this.llama.isLeashed() && !this.llama.inCaravan()) { ++ // Leaf start - Async Target Finding ++ Llama llama = poll(); ++ double d = Double.MAX_VALUE; ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ if (llama == null) { ++ findTargetAsync(); ++ return false; ++ } else { ++ d = this.llama.distanceToSqr(llama); ++ } ++ } else { ++ // Leaf end - Async Target Finding + List entities = this.llama.level().getEntities(this.llama, this.llama.getBoundingBox().inflate(9.0, 4.0, 9.0), entity1 -> { + EntityType type = entity1.getType(); + return type == EntityType.LLAMA || type == EntityType.TRADER_LLAMA; + }); +- Llama llama = null; +- double d = Double.MAX_VALUE; ++ // Llama llama = null; // Leaf ++ // double d = Double.MAX_VALUE; // Leaf + + for (Entity entity : entities) { + Llama llama1 = (Llama)entity; ++ // Leaf - Async Target Finding + if (llama1.inCaravan() && !llama1.hasCaravanTail()) { + double d1 = this.llama.distanceToSqr(llama1); + if (!(d1 > d)) { +@@ -45,6 +136,7 @@ public class LlamaFollowCaravanGoal extends Goal { + if (llama == null) { + for (Entity entityx : entities) { + Llama llama1 = (Llama)entityx; ++ // Leaf - Async Target Finding + if (llama1.isLeashed() && !llama1.hasCaravanTail()) { + double d1 = this.llama.distanceToSqr(llama1); + if (!(d1 > d)) { +@@ -54,6 +146,7 @@ public class LlamaFollowCaravanGoal extends Goal { + } + } + } ++ } // Leaf + + if (llama == null) { + return false; +diff --git a/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java b/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java +index 6463c3c9b08d6058f2843c225b08a40fc30a960b..0f39eca165f3eae1b05d1e0548f63467bc21bbf5 100644 +--- a/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java ++++ b/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java +@@ -48,32 +48,123 @@ public class LookAtPlayerGoal extends Goal { + + @Override + public boolean canUse() { ++ // Leaf start - Async look finding ++ if (poll()) { ++ return true; ++ } ++ + if (this.mob.getRandom().nextFloat() >= this.probability) { + return false; +- } else { +- if (this.mob.getTarget() != null) { +- this.lookAt = this.mob.getTarget(); ++ } ++ if (this.lookAtType == Player.class) { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchPlayer) { ++ getLookAsync(); ++ return false; + } ++ } else if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ getLookAsync(); ++ return false; ++ } + +- ServerLevel serverLevel = getServerLevel(this.mob); +- if (this.lookAtType == Player.class) { +- this.lookAt = serverLevel.getNearestPlayer(this.lookAtContext, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); +- } else { +- this.lookAt = serverLevel.getNearestEntity( +- this.mob +- .level() +- .getEntitiesOfClass(this.lookAtType, this.mob.getBoundingBox().inflate(this.lookDistance, 3.0, this.lookDistance), livingEntity -> true), +- this.lookAtContext, +- this.mob, +- this.mob.getX(), +- this.mob.getEyeY(), +- this.mob.getZ() +- ); +- } ++ if (this.mob.getTarget() != null) { ++ this.lookAt = this.mob.getTarget(); ++ } ++ ++ ServerLevel serverLevel = getServerLevel(this.mob); ++ if (this.lookAtType == Player.class) { ++ this.lookAt = serverLevel.getNearestPlayer(this.lookAtContext, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); ++ } else { ++ this.lookAt = serverLevel.getNearestEntity( ++ this.mob ++ .level() ++ .getEntitiesOfClass(this.lookAtType, this.mob.getBoundingBox().inflate(this.lookDistance, 3.0, this.lookDistance), livingEntity -> true), ++ this.lookAtContext, ++ this.mob, ++ this.mob.getX(), ++ this.mob.getEyeY(), ++ this.mob.getZ() ++ ); ++ } ++ ++ return this.lookAt != null; ++ // Leaf end - Async look finding ++ } + +- return this.lookAt != null; ++ // Leaf start - Async look finding ++ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); ++ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); ++ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; ++ protected boolean poll() { ++ LivingEntity t = pendingTarget.getAndSet(null); ++ if (t == null) { ++ return false; ++ } ++ if (this.mob.getTarget() != null) { ++ this.lookAt = this.mob.getTarget(); ++ return true; ++ } ++ ServerLevel serverLevel = getServerLevel(this.mob); ++ if (!t.isAlive() || !this.lookAtContext.test(serverLevel, this.mob, t)) { ++ return false; ++ } ++ this.lookAt = t; ++ return true; ++ } ++ ++ protected void getLookAsync() { ++ if (!isSearching.compareAndSet(false, true)) { ++ return; + } ++ ++ final var mob = this.mob; ++ ++ if (mob == null || mob.isRemoved() || !mob.isAlive()) { ++ isSearching.setRelease(false); ++ return; ++ } ++ ++ final double x = mob.getX(); ++ final double y = mob.getEyeY(); ++ final double z = mob.getZ(); ++ final var serverLevel = getServerLevel(mob); ++ final var bound = mob.getBoundingBox().inflate(this.lookDistance, 3.0, this.lookDistance); ++ final var target = mob.getTarget(); ++ final var lookAtContext = this.lookAtContext; ++ final var lookAtType = this.lookAtType; ++ final var pendingTarget = this.pendingTarget; ++ serverLevel.asyncAITasks.add(() -> { ++ try { ++ if (mob == null || !mob.isAlive() || mob.level() != serverLevel) { ++ return; ++ } ++ if (target != null) { ++ pendingTarget.setRelease(target); ++ return; ++ } ++ ++ if (lookAtType == Player.class) { ++ var result = serverLevel.getNearestPlayer(lookAtContext, mob, x, y, z); ++ pendingTarget.setRelease(result); ++ } else { ++ var result = serverLevel.getNearestEntity( ++ serverLevel ++ .getEntitiesOfClass(lookAtType, bound, livingEntity -> true), ++ lookAtContext, ++ mob, ++ x, ++ y, ++ z ++ ); ++ pendingTarget.setRelease(result); ++ } ++ } catch (Exception e) { ++ LOGGER.error("Exception getting entities", e); ++ } finally { ++ isSearching.setRelease(false); ++ } ++ }); + } ++ // Leaf end - Async look finding + + @Override + public boolean canContinueToUse() { +diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..6ff92396c6c97f4185e287f04eb197842d8b5fec 100644 +--- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java ++++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +@@ -41,8 +41,90 @@ public abstract class MoveToBlockGoal extends Goal { + this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.JUMP)); + } + ++ // Leaf start - Async search block ++ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); ++ private final java.util.concurrent.atomic.AtomicReference pendingBlock = new java.util.concurrent.atomic.AtomicReference<>(null); ++ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; ++ protected boolean poll() { ++ BlockPos blockPos1 = pendingBlock.getAndSet(null); ++ if (blockPos1 == null) { ++ return false; ++ } ++ if (!this.mob.level().hasChunkAt(blockPos1) ++ || !this.mob.isWithinRestriction(blockPos1) ++ || !this.isValidTarget(this.mob.level(), blockPos1)) { ++ return false; ++ } ++ this.blockPos = blockPos1; ++ this.mob.movingTarget = blockPos1 == BlockPos.ZERO ? null : blockPos1; ++ return true; ++ } ++ ++ protected void getBlockAsync() { ++ if (!isSearching.compareAndSet(false, true)) { ++ return; ++ } ++ ++ final var mob = this.mob; ++ ++ if (mob == null || mob.isRemoved() || !mob.isAlive()) { ++ isSearching.setRelease(false); ++ return; ++ } ++ ++ final double x = mob.getX(); ++ final double y = mob.getEyeY(); ++ final double z = mob.getZ(); ++ final var serverLevel = getServerLevel(mob); ++ ++ final java.util.concurrent.atomic.AtomicReference pendingBlock = this.pendingBlock; ++ final TypeToCheck ty = this.typeToCheck(); ++ final net.minecraft.world.level.block.Block toRemove; ++ if (this instanceof RemoveBlockGoal removeBlockGoal) { ++ toRemove = removeBlockGoal.blockToRemove; ++ } else { ++ toRemove = null; ++ } ++ final int verticalSearchStart = this.verticalSearchStart; ++ final int searchRange = this.searchRange; ++ final int verticalSearchRange = this.verticalSearchRange; ++ final BlockPos blockPos = mob.blockPosition(); ++ final float restrictRadius = mob.getRestrictRadius(); ++ final BlockPos restrictCenter = mob.getRestrictCenter(); ++ serverLevel.asyncAITasks.add(() -> { ++ try { ++ if (mob == null || !mob.isAlive() || mob.level() != serverLevel) { ++ return; ++ } ++ findNearestBlockAsync(pendingBlock, ty, toRemove, mob, serverLevel, verticalSearchStart, searchRange, verticalSearchRange, blockPos, restrictRadius, restrictCenter); ++ } catch (Exception e) { ++ LOGGER.error("Exception getting block", e); ++ } finally { ++ isSearching.setRelease(false); ++ } ++ }); ++ } ++ ++ protected enum TypeToCheck { ++ CatLie, ++ CatSit, ++ Drowned, ++ FoxEat, ++ RaidGarden, ++ RemoveBlock, ++ Strider, ++ TurtleToWater, ++ TurtleLay, ++ } ++ // Leaf end - Async search block ++ + @Override + public boolean canUse() { ++ // Leaf start - Async search block ++ if (poll()) { ++ return true; ++ } ++ // Leaf end - Async search block + if (this.nextStartTick > 0) { + this.nextStartTick--; + return false; +@@ -109,6 +191,10 @@ public abstract class MoveToBlockGoal extends Goal { + } + + protected boolean findNearestBlock() { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchBlock) { ++ getBlockAsync(); ++ return false; ++ } + int i = this.searchRange; + int i1 = this.verticalSearchRange; + BlockPos blockPos = this.mob.blockPosition(); +@@ -133,5 +219,104 @@ public abstract class MoveToBlockGoal extends Goal { + return false; + } + ++ protected static boolean findNearestBlockAsync( ++ final java.util.concurrent.atomic.AtomicReference pendingBlock, ++ final TypeToCheck ty, ++ @org.jetbrains.annotations.Nullable final net.minecraft.world.level.block.Block toRemove, ++ final PathfinderMob mob, ++ final net.minecraft.server.level.ServerLevel serverLevel, ++ final int verticalSearchStart, ++ final int searchRange, ++ final int verticalSearchRange, ++ final BlockPos blockPos, ++ final float restrictRadius, ++ final BlockPos restrictCenter ++ ) { ++ BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); ++ for (int i2 = verticalSearchStart; i2 <= verticalSearchRange; i2 = i2 > 0 ? -i2 : 1 - i2) { ++ for (int i3 = 0; i3 < searchRange; i3++) { ++ for (int i4 = 0; i4 <= i3; i4 = i4 > 0 ? -i4 : 1 - i4) { ++ for (int i5 = i4 < i3 && i4 > -i3 ? i3 : 0; i5 <= i3; i5 = i5 > 0 ? -i5 : 1 - i5) { ++ mutableBlockPos.setWithOffset(blockPos, i4, i2 - 1, i5); ++ if (!serverLevel.hasChunkAt(mutableBlockPos)) continue; ++ if (isWithinRestriction(restrictRadius, restrictCenter, mutableBlockPos) ++ && isValidTargetAsync(ty, toRemove, mob, serverLevel, mutableBlockPos)) { ++ pendingBlock.setRelease(mutableBlockPos.immutable()); ++ return true; ++ } ++ } ++ } ++ } ++ } ++ ++ return false; ++ } ++ ++ private static boolean isWithinRestriction(float restrictRadius, BlockPos restrictCenter, BlockPos pos) { ++ return restrictRadius == -1.0F || restrictCenter.distSqr(pos) < restrictRadius * restrictRadius; ++ } ++ + protected abstract boolean isValidTarget(LevelReader level, BlockPos pos); ++ ++ protected abstract TypeToCheck typeToCheck(); ++ ++ private static boolean isValidTargetAsync( ++ TypeToCheck ty, ++ @org.jetbrains.annotations.Nullable net.minecraft.world.level.block.Block blockToRemoveTy, ++ PathfinderMob mob, ++ LevelReader level, ++ BlockPos pos ++ ) { ++ switch (ty) { ++ case CatLie -> { ++ return level.isEmptyBlock(pos.above()) && level.getBlockState(pos).is(net.minecraft.tags.BlockTags.BEDS); ++ } ++ case CatSit -> { ++ if (!level.isEmptyBlock(pos.above())) { ++ return false; ++ } else { ++ var blockState = level.getBlockState(pos); ++ return blockState.is(net.minecraft.world.level.block.Blocks.CHEST) ++ ? net.minecraft.world.level.block.entity.ChestBlockEntity.getOpenCount(level, pos) < 1 ++ : blockState.is(net.minecraft.world.level.block.Blocks.FURNACE) && blockState.getValue(net.minecraft.world.level.block.FurnaceBlock.LIT) ++ || blockState.is(net.minecraft.tags.BlockTags.BEDS, state -> state.getOptionalValue(net.minecraft.world.level.block.BedBlock.PART).map(bedPart -> bedPart != net.minecraft.world.level.block.state.properties.BedPart.HEAD).orElse(true)); ++ } ++ } ++ case Drowned -> { ++ BlockPos blockPos = pos.above(); ++ return level.isEmptyBlock(blockPos) && level.isEmptyBlock(blockPos.above()) && level.getBlockState(pos).entityCanStandOn(level, pos, mob); ++ } ++ case FoxEat -> { ++ var blockState = level.getBlockState(pos); ++ return blockState.is(net.minecraft.world.level.block.Blocks.SWEET_BERRY_BUSH) && blockState.getValue(net.minecraft.world.level.block.SweetBerryBushBlock.AGE) >= 2 || net.minecraft.world.level.block.CaveVines.hasGlowBerries(blockState); ++ } ++ case RaidGarden -> { ++ var blockState = level.getBlockState(pos); ++ if (blockState.is(net.minecraft.world.level.block.Blocks.FARMLAND)) { ++ blockState = level.getBlockState(pos.above()); ++ return blockState.getBlock() instanceof net.minecraft.world.level.block.CarrotBlock carrot && carrot.isMaxAge(blockState); ++ } else { ++ return false; ++ } ++ ++ } ++ case RemoveBlock -> { ++ var chunk = level.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4); // Paper - Prevent AI rules from loading chunks ++ return chunk != null ++ && chunk.getBlockState(pos).is(blockToRemoveTy) ++ && chunk.getBlockState(pos.above()).isAir() ++ && chunk.getBlockState(pos.above(2)).isAir(); ++ } ++ case Strider -> { ++ return level.getBlockState(pos).is(net.minecraft.world.level.block.Blocks.LAVA) && level.getBlockState(pos.above()).isPathfindable(net.minecraft.world.level.pathfinder.PathComputationType.LAND); ++ } ++ case TurtleToWater -> { ++ return level.getBlockState(pos).is(net.minecraft.world.level.block.Blocks.WATER); ++ } ++ case TurtleLay -> { ++ return level.isEmptyBlock(pos.above()) && net.minecraft.world.level.block.TurtleEggBlock.isSand(level, pos); ++ } ++ case null -> throw new IllegalStateException(); ++ } ++ } + } +diff --git a/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java b/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java +index 3c274d917bca9de87abfb842f5f332e112a7a2d7..f001bd12a2ec5696b8ee628484597de09b2eed32 100644 +--- a/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java ++++ b/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java +@@ -19,10 +19,20 @@ public class OfferFlowerGoal extends Goal { + + @Override + public boolean canUse() { ++ // Leaf start - Async offer flower finding + if (!this.golem.level().isDay()) { + return false; +- } else if (this.golem.getRandom().nextInt(8000) != 0) { ++ } ++ if (poll()) { ++ return true; ++ } ++ if (this.golem.getRandom().nextInt(8000) != 0) { + return false; ++ } ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ getVillagerAsync(); ++ return false; ++ // Leaf end - Async offer flower finding + } else { + this.villager = getServerLevel(this.golem) + .getNearestEntity( +@@ -38,6 +48,65 @@ public class OfferFlowerGoal extends Goal { + } + } + ++ ++ // Leaf start - Async look finding ++ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); ++ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); ++ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; ++ protected boolean poll() { ++ Villager t = pendingTarget.getAndSet(null); ++ if (t == null) { ++ return false; ++ } ++ var serverLevel = getServerLevel(this.golem); ++ if (!t.isAlive() || !OFFER_TARGER_CONTEXT.test(serverLevel, this.golem, t)) { ++ return false; ++ } ++ this.villager = t; ++ return true; ++ } ++ ++ protected void getVillagerAsync() { ++ if (!isSearching.compareAndSet(false, true)) { ++ return; ++ } ++ ++ final var golem = this.golem; ++ ++ if (golem == null || golem.isRemoved() || !golem.isAlive()) { ++ isSearching.setRelease(false); ++ return; ++ } ++ ++ final double x = golem.getX(); ++ final double y = golem.getEyeY(); ++ final double z = golem.getZ(); ++ final var serverLevel = getServerLevel(golem); ++ final var bound = golem.getBoundingBox().inflate(6.0, 2.0, 6.0); ++ final var pendingTarget = this.pendingTarget; ++ serverLevel.asyncAITasks.add(() -> { ++ try { ++ if (golem == null || !golem.isAlive() || golem.level() != serverLevel) { ++ return; ++ } ++ var result = serverLevel.getNearestEntity( ++ serverLevel.getEntitiesOfClass(Villager.class, bound, livingEntity -> true), ++ OFFER_TARGER_CONTEXT, ++ golem, ++ x, ++ y, ++ z ++ ); ++ pendingTarget.setRelease(result); ++ } catch (Exception e) { ++ LOGGER.error("Exception getting entities", e); ++ } finally { ++ isSearching.setRelease(false); ++ } ++ }); ++ } ++ // Leaf end - Async look finding ++ + @Override + public boolean canContinueToUse() { + return this.tick > 0; +diff --git a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java +index 95fa516910a3834bbd4db6d11279e13a1f0dac41..f7ddae601abbe9e22a35c7cb4f9763e61fa7ff81 100644 +--- a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java ++++ b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java +@@ -22,7 +22,7 @@ import net.minecraft.world.level.chunk.status.ChunkStatus; + import net.minecraft.world.phys.Vec3; + + public class RemoveBlockGoal extends MoveToBlockGoal { +- private final Block blockToRemove; ++ protected final Block blockToRemove; // Leaf - Async search block + private final Mob removerMob; + private int ticksSinceReachedGoal; + private static final int WAIT_AFTER_BLOCK_FOUND = 20; +@@ -37,7 +37,14 @@ public class RemoveBlockGoal extends MoveToBlockGoal { + public boolean canUse() { + if (!getServerLevel(this.removerMob).purpurConfig.zombieBypassMobGriefing == !getServerLevel(this.removerMob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected + return false; +- } else if (this.nextStartTick > 0) { ++ } ++ // Leaf start - async search block ++ if (poll()) { ++ this.nextStartTick = reducedTickDelay(20); ++ return true; ++ } ++ // Leaf end - async search block ++ if (this.nextStartTick > 0) { + this.nextStartTick--; + return false; + } else if (this.findNearestBlock()) { +@@ -149,10 +156,18 @@ public class RemoveBlockGoal extends MoveToBlockGoal { + + @Override + protected boolean isValidTarget(LevelReader level, BlockPos pos) { ++ // Leaf - Async search block + ChunkAccess chunk = level.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4); // Paper - Prevent AI rules from loading chunks + return chunk != null + && chunk.getBlockState(pos).is(this.blockToRemove) + && chunk.getBlockState(pos.above()).isAir() + && chunk.getBlockState(pos.above(2)).isAir(); + } ++ ++ // Leaf start - Async search block ++ @Override ++ protected TypeToCheck typeToCheck() { ++ return TypeToCheck.RemoveBlock; ++ } ++ // Leaf end - Async search block + } +diff --git a/net/minecraft/world/entity/ai/goal/TemptGoal.java b/net/minecraft/world/entity/ai/goal/TemptGoal.java +index f88f618d34fb343b31de3af1a875d6633703df71..7c6d90ed1878b29904ceaca1089eaadd05a0d570 100644 +--- a/net/minecraft/world/entity/ai/goal/TemptGoal.java ++++ b/net/minecraft/world/entity/ai/goal/TemptGoal.java +@@ -36,14 +36,84 @@ public class TemptGoal extends Goal { + this.targetingConditions = TEMPT_TARGETING.copy().selector((entity, level) -> this.shouldFollow(entity)); + } + ++ // Leaf start - Async Tempt Finding ++ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); ++ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); ++ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; ++ ++ private boolean poll() { ++ LivingEntity t = pendingTarget.getAndSet(null); ++ if (t == null) { ++ t = this.player; ++ } ++ if (t == null) { ++ return false; ++ } ++ var serverLevel = getServerLevel(this.mob); ++ if (serverLevel == null || !t.isAlive() || !this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)).test(serverLevel, this.mob, t)) { ++ return false; ++ } ++ this.player = t; ++ return true; ++ } ++ ++ private void getNearestPlayerAsync() { ++ if (!isSearching.compareAndSet(false, true)) { ++ return; ++ } ++ ++ final var mob = this.mob; ++ ++ if (mob == null || mob.isRemoved() || !mob.isAlive()) { ++ isSearching.setRelease(false); ++ return; ++ } ++ ++ final var serverLevel = getServerLevel(mob); ++ final var pendingTarget = this.pendingTarget; ++ final var conditions = this.targetingConditions ++ .range(mob.getAttributeValue(Attributes.TEMPT_RANGE)) ++ .copy(); ++ serverLevel.asyncAITasks.add(() -> { ++ try { ++ if (mob == null || mob.isRemoved() || !mob.isAlive() || mob.level() != serverLevel) { ++ return; ++ } ++ pendingTarget.setRelease(serverLevel.getNearestPlayer(conditions, mob)); ++ } catch (Exception e) { ++ LOGGER.error("Exception getting player", e); ++ } finally { ++ isSearching.setRelease(false); ++ } ++ }); ++ } ++ // Leaf end - Async Tempt Finding + @Override + public boolean canUse() { + if (this.calmDown > 0) { + this.calmDown--; + return false; + } else { +- this.player = getServerLevel(this.mob) +- .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); ++ // Leaf start - Async Tempt Finding ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchPlayerTempt) { ++ if (poll()) { ++ if (this.player != null) { ++ org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT); ++ if (event.isCancelled()) { ++ return false; ++ } ++ this.player = (event.getTarget() == null) ? null : ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle(); ++ } ++ return this.player != null; ++ } else { ++ getNearestPlayerAsync(); ++ return false; ++ } ++ } else { ++ this.player = getServerLevel(this.mob) ++ .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); ++ } ++ // Leaf end - Async Tempt Finding + // CraftBukkit start + if (this.player != null) { + org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT); +diff --git a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java +index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..04bc007a138d5f5875fa89f52d573693fa96c656 100644 +--- a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java ++++ b/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java +@@ -16,7 +16,7 @@ public class DefendVillageTargetGoal extends TargetGoal { + private final IronGolem golem; + @Nullable + private LivingEntity potentialTarget; +- private final TargetingConditions attackTargeting = TargetingConditions.forCombat().range(64.0); ++ private static final TargetingConditions attackTargeting = TargetingConditions.forCombat().range(64.0); // Leaf - static + + public DefendVillageTargetGoal(IronGolem golem) { + super(golem, false, true); +@@ -24,8 +24,82 @@ public class DefendVillageTargetGoal extends TargetGoal { + this.setFlags(EnumSet.of(Goal.Flag.TARGET)); + } + ++ // Leaf start - Async Target Finding ++ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; ++ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); ++ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); ++ protected boolean poll() { ++ LivingEntity t = pendingTarget.getAndSet(null); ++ if (t == null) { ++ return false; ++ } ++ ServerLevel serverLevel = getServerLevel(this.mob); ++ if (serverLevel == null || !t.isAlive() || !attackTargeting.test(serverLevel, golem, t)) { ++ return false; ++ } ++ if ((t instanceof Player player && (player.isSpectator() || player.isCreative()))) { ++ return false; ++ } ++ this.potentialTarget = t; ++ ++ return true; ++ } ++ ++ private void findTargetAsync() { ++ if (!isSearching.compareAndSet(false, true)) { ++ return; ++ } ++ ++ final IronGolem mob = this.golem; ++ if (mob == null || mob.isRemoved() || !mob.isAlive()) { ++ isSearching.setRelease(false); ++ return; ++ } ++ ++ final double x = mob.getX(); ++ final double y = mob.getEyeY(); ++ final double z = mob.getZ(); ++ AABB bound = this.golem.getBoundingBox().inflate(10.0, 8.0, 10.0); ++ final ServerLevel serverLevel = getServerLevel(mob); ++ final var pendingTarget = this.pendingTarget; ++ ++ serverLevel.asyncAITasks.add(() -> { ++ try { ++ if (mob.level() != serverLevel || !mob.isAlive()) { ++ return; ++ } ++ ++ List nearbyEntities = serverLevel.getNearbyEntities(Villager.class, attackTargeting, mob, bound); ++ List nearbyPlayers = serverLevel.getNearbyPlayers(attackTargeting, mob, bound); ++ ++ for (Villager villager : nearbyEntities) { ++ for (Player player : nearbyPlayers) { ++ int playerReputation = villager.getPlayerReputation(player); ++ if (playerReputation <= -100) { ++ pendingTarget.setRelease(player); ++ } ++ } ++ } ++ } catch (Exception e) { ++ LOGGER.error("Exception getting entities", e); ++ } finally { ++ isSearching.setRelease(false); ++ } ++ }); ++ } ++ // Leaf end - Async Target Finding ++ + @Override + public boolean canUse() { ++ // Leaf start - Async target finding ++ if (poll()) { ++ return true; ++ } ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ this.findTargetAsync(); ++ return false; ++ } ++ // Leaf end - Async target finding + AABB aabb = this.golem.getBoundingBox().inflate(10.0, 8.0, 10.0); + ServerLevel serverLevel = getServerLevel(this.golem); + List nearbyEntities = serverLevel.getNearbyEntities(Villager.class, this.attackTargeting, this.golem, aabb); +@@ -42,6 +116,7 @@ public class DefendVillageTargetGoal extends TargetGoal { + } + } + ++ // Leaf - Async target finding + return this.potentialTarget != null + && (!(this.potentialTarget instanceof Player) || !this.potentialTarget.isSpectator() && !((Player)this.potentialTarget).isCreative()); + } +diff --git a/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java +index 25fe78116ce01eeefe5c958423734195d27302eb..50228ccc92181d021b2f1cf164671ce4b2095c75 100644 +--- a/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java ++++ b/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java +@@ -73,6 +73,59 @@ public class HurtByTargetGoal extends TargetGoal { + protected void alertOthers() { + double followDistance = this.getFollowDistance(); + AABB aabb = AABB.unitCubeFromLowerCorner(this.mob.position()).inflate(followDistance, 10.0, followDistance); ++ ++ // Leaf start - async alert other ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.alertOther) { ++ final var self = this.mob; ++ final var serverLevel = getServerLevel(self); ++ final var alertTarget = this.mob.getLastHurtByMob(); ++ final var toIgnoreAlert = this.toIgnoreAlert; ++ serverLevel.asyncAITasks.add(() -> { ++ try { ++ if (alertTarget == null || self == null || self.level() != serverLevel) { ++ return; ++ } ++ var toAlert = new java.util.ArrayList(); ++ List entitiesOfClass = serverLevel ++ .getEntitiesOfClass(self.getClass(), aabb, EntitySelector.NO_SPECTATORS); ++ for (Mob mob : entitiesOfClass) { ++ if (self != mob ++ && mob.getTarget() == null ++ && (!(self instanceof TamableAnimal) || ((TamableAnimal) self).getOwner() == ((TamableAnimal) mob).getOwner()) ++ && !mob.isAlliedTo(self.getLastHurtByMob())) { ++ if (toIgnoreAlert == null) { ++ continue; ++ } ++ ++ boolean flag = false; ++ ++ for (Class clazz : toIgnoreAlert) { ++ if (mob.getClass() == clazz) { ++ flag = true; ++ break; ++ } ++ } ++ ++ if (!flag) { ++ continue; ++ } ++ ++ toAlert.add(mob); ++ } ++ } ++ serverLevel.getServer().execute(() -> { ++ for (var livingEntity : toAlert) { ++ alertOther(livingEntity, alertTarget); ++ } ++ }); ++ } catch (Exception e) { ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception alertOthers", e); ++ } ++ }); ++ return; ++ } ++ // Leaf end - async alert other ++ + List entitiesOfClass = this.mob + .level() + .getEntitiesOfClass((Class)this.mob.getClass(), aabb, EntitySelector.NO_SPECTATORS); +@@ -86,6 +139,7 @@ public class HurtByTargetGoal extends TargetGoal { + } + + mob = (Mob)var5.next(); ++ // Leaf - async alert other + if (this.mob != mob + && mob.getTarget() == null + && (!(this.mob instanceof TamableAnimal) || ((TamableAnimal)this.mob).getOwner() == ((TamableAnimal)mob).getOwner()) +@@ -96,6 +150,7 @@ public class HurtByTargetGoal extends TargetGoal { + + boolean flag = false; + ++ // Leaf - async alert other + for (Class clazz : this.toIgnoreAlert) { + if (mob.getClass() == clazz) { + flag = true; +diff --git a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java +index 41ee3cdc45ecc8376a2203ed588bb544ed377294..24c7567ffe88d4767371ac0082aa26cf5d2c2ed8 100644 +--- a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java ++++ b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java +@@ -41,8 +41,73 @@ public class NearestAttackableTargetGoal extends TargetG + this.targetConditions = TargetingConditions.forCombat().range(this.getFollowDistance()).selector(selector); + } + ++ // Leaf start - Async Target Finding ++ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; ++ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); ++ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); ++ protected boolean poll() { ++ LivingEntity t = pendingTarget.getAndSet(null); ++ if (t == null) { ++ return false; ++ } ++ ServerLevel serverLevel = getServerLevel(this.mob); ++ if (serverLevel == null || !t.isAlive() || !this.getTargetConditions().test(serverLevel, this.mob, t)) { ++ return false; ++ } ++ this.target = t; ++ return true; ++ } ++ ++ private void findTargetAsync() { ++ if (!isSearching.compareAndSet(false, true)) { ++ return; ++ } ++ ++ final Mob mob = this.mob; ++ if (mob == null || mob.isRemoved() || !mob.isAlive()) { ++ isSearching.setRelease(false); ++ return; ++ } ++ ++ final double x = mob.getX(); ++ final double y = mob.getEyeY(); ++ final double z = mob.getZ(); ++ final TargetingConditions targetConditions = this.getTargetConditions().copy(); ++ final Class targetType = this.targetType; ++ final AABB targetSearch = getTargetSearchArea(this.getFollowDistance()); ++ final ServerLevel serverLevel = getServerLevel(mob); ++ final var pendingTarget = this.pendingTarget; ++ ++ serverLevel.asyncAITasks.add(() -> { ++ try { ++ if (mob.level() != serverLevel || mob.isRemoved() || !mob.isAlive()) { ++ return; ++ } ++ ++ if (targetType != Player.class && targetType != ServerPlayer.class) { ++ final java.util.List entities = mob.level().getEntitiesOfClass(targetType, targetSearch, entity -> entity != null && entity != mob && entity.isAlive()); ++ var result = serverLevel.getNearestEntity(entities, targetConditions, mob, x,y,z); ++ pendingTarget.setRelease(result); ++ } else { ++ var result = serverLevel.getNearestPlayer(targetConditions, mob, x, y, z); ++ pendingTarget.setRelease(result); ++ } ++ } catch (Exception e) { ++ LOGGER.error("Exception getting entities", e); ++ } finally { ++ isSearching.setRelease(false); ++ } ++ }); ++ } ++ // Leaf end - Async Target Finding ++ + @Override + public boolean canUse() { ++ // Leaf start - Async target finding ++ if (poll()) { ++ return true; ++ } ++ // Leaf end - Async target finding + if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) { + return false; + } else { +@@ -57,6 +122,21 @@ public class NearestAttackableTargetGoal extends TargetG + + protected void findTarget() { + ServerLevel serverLevel = getServerLevel(this.mob); ++ ++ // Leaf start - Async Target Finding ++ if (targetType == Player.class) { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchPlayer) { ++ this.findTargetAsync(); ++ this.target = null; ++ return; ++ } ++ } else if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ this.findTargetAsync(); ++ this.target = null; ++ return; ++ } ++ // Leaf end - Async Target Finding ++ + if (this.targetType != Player.class && this.targetType != ServerPlayer.class) { + this.target = serverLevel.getNearestEntity( + this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true), +@@ -81,7 +161,7 @@ public class NearestAttackableTargetGoal extends TargetG + this.target = target; + } + +- private TargetingConditions getTargetConditions() { ++ protected TargetingConditions getTargetConditions() { + return this.targetConditions.range(this.getFollowDistance()); + } + } +diff --git a/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java +index 4604a603c4ddd0a9242e859aaa5a511c2d4c4f84..7274dfb52ca4a08cdebcd04294cedc73460593e5 100644 +--- a/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java ++++ b/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java +@@ -23,7 +23,15 @@ public class NearestHealableRaiderTargetGoal extends Nea + + @Override + public boolean canUse() { +- if (this.cooldown > 0 || !this.mob.getRandom().nextBoolean()) { ++ // Leaf start - Async target finding ++ if (this.cooldown > 0) { ++ return false; ++ } ++ if (poll()) { ++ return ((Raider) this.mob).hasActiveRaid(); ++ } ++ if (/*this.cooldown > 0 || */ !this.mob.getRandom().nextBoolean()) { ++ // Leaf end - Async target finding + return false; + } else if (!((Raider)this.mob).hasActiveRaid()) { + return false; +diff --git a/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java +index abf57494950f55bbd75f335f26736cb9e703c197..683722fbd07fcae9cdd72928ed25fd084202b57e 100644 +--- a/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java ++++ b/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java +@@ -20,6 +20,15 @@ public class NonTameRandomTargetGoal extends NearestAtta + + @Override + public boolean canContinueToUse() { +- return this.targetConditions != null ? this.targetConditions.test(getServerLevel(this.mob), this.mob, this.target) : super.canContinueToUse(); ++ // Leaf start ++ if (this.target == null || !this.target.isAlive() || this.target.isRemoved()) { ++ return false; ++ } ++ var serverLevel = getServerLevel(this.mob); ++ if (serverLevel == null) { ++ return false; ++ } ++ return this.getTargetConditions().test(serverLevel, this.mob, this.target) && super.canContinueToUse(); ++ // Leaf end + } + } +diff --git a/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java +index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..8db8dc0b9a79bc56568e22a8e99354de599ed23b 100644 +--- a/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java ++++ b/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java +@@ -37,6 +37,36 @@ public class ResetUniversalAngerTargetGoal extends G + this.lastHurtByPlayerTimestamp = this.mob.getLastHurtByMobTimestamp(); + this.mob.forgetCurrentTargetAndRefreshUniversalAnger(); + if (this.alertOthersOfSameType) { ++ // Leaf start - async alert other ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.alertOther) { ++ final var mob = this.mob; ++ final var serverLevel = getServerLevel(mob); ++ final double followRange = this.mob.getAttributeValue(Attributes.FOLLOW_RANGE); ++ final AABB aabb = AABB.unitCubeFromLowerCorner(this.mob.position()).inflate(followRange, 10.0, followRange); ++ serverLevel.asyncAITasks.add(() -> { ++ try { ++ if (mob == null || serverLevel != mob.level() || !mob.isAlive()) { ++ return; ++ } ++ var entities = serverLevel.getEntitiesOfClass(mob.getClass(), aabb, EntitySelector.NO_SPECTATORS); ++ List toStop = new java.util.ArrayList<>(entities.size()); ++ for (Mob entity : entities) { ++ if (entity != mob) { ++ toStop.add((NeutralMob) entity); ++ } ++ } ++ serverLevel.getServer().execute(() -> { ++ for (NeutralMob neutralMob : toStop) { ++ neutralMob.forgetCurrentTargetAndRefreshUniversalAnger(); ++ } ++ }); ++ } catch (Exception e) { ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception alertOthers forgetCurrentTargetAndRefreshUniversalAnger", e); ++ } ++ }); ++ return; ++ } ++ // Leaf end - async alert other + this.getNearbyMobsOfSameType() + .stream() + .filter(mob -> mob != this.mob) +@@ -48,6 +78,7 @@ public class ResetUniversalAngerTargetGoal extends G + } + + private List getNearbyMobsOfSameType() { ++ // Leaf - async alert other + double attributeValue = this.mob.getAttributeValue(Attributes.FOLLOW_RANGE); + AABB aabb = AABB.unitCubeFromLowerCorner(this.mob.position()).inflate(attributeValue, 10.0, attributeValue); + return this.mob.level().getEntitiesOfClass((Class)this.mob.getClass(), aabb, EntitySelector.NO_SPECTATORS); +diff --git a/net/minecraft/world/entity/ai/sensing/Sensing.java b/net/minecraft/world/entity/ai/sensing/Sensing.java +index 002d3c0d8b1107a275020d5c582c37e9a5c536ee..3f8c18f4040f4929df79ba85906330b150720cb0 100644 +--- a/net/minecraft/world/entity/ai/sensing/Sensing.java ++++ b/net/minecraft/world/entity/ai/sensing/Sensing.java +@@ -32,9 +32,21 @@ public class Sensing { + // Gale end - Petal - reduce line of sight updates - expiring entity id lists + } + ++ // Leaf start - async target finding + public void tick() { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ synchronized (this) { ++ tick1(); ++ } ++ } else { ++ tick1(); ++ } ++ } ++ ++ private void tick1() { ++ // Leaf end - async target finding + if (this.expiring == null) { // Gale - Petal - reduce line of sight updates +- this.seen.clear(); ++ this.seen.clear(); + // Gale start - Petal - reduce line of sight updates + } else { + var expiringNow = this.expiring[this.nextToExpireIndex]; +@@ -62,7 +74,19 @@ public class Sensing { + // Gale end - Petal - reduce line of sight updates + } + ++ // Leaf start - async target finding + public boolean hasLineOfSight(Entity entity) { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ synchronized (this) { ++ return hasLineOfSight1(entity); ++ } ++ } else { ++ return hasLineOfSight1(entity); ++ } ++ } ++ ++ private boolean hasLineOfSight1(Entity entity) { ++ // Leaf end - async target finding + int id = entity.getId(); + // Gale start - Petal - reduce line of sight cache lookups - merge sets + int cached = this.seen.get(id); +diff --git a/net/minecraft/world/entity/animal/Fox.java b/net/minecraft/world/entity/animal/Fox.java +index 90452f0945e761077608692877677f522d38bccd..9e380f5b4dcedb115f8893dd382f28ea05fab121 100644 +--- a/net/minecraft/world/entity/animal/Fox.java ++++ b/net/minecraft/world/entity/animal/Fox.java +@@ -849,13 +849,18 @@ public class Fox extends Animal implements VariantHolder { + return false; + } else { + ServerLevel serverLevel = getServerLevel(Fox.this.level()); ++ // Leaf start ++ if (serverLevel == null) { ++ return false; ++ } ++ // Leaf end + + for (UUID uuid : Fox.this.getTrustedUUIDs()) { + if (serverLevel.getEntity(uuid) instanceof LivingEntity livingEntity) { + this.trustedLastHurt = livingEntity; + this.trustedLastHurtBy = livingEntity.getLastHurtByMob(); + int lastHurtByMobTimestamp = livingEntity.getLastHurtByMobTimestamp(); +- return lastHurtByMobTimestamp != this.timestamp && this.canAttack(this.trustedLastHurtBy, this.targetConditions); ++ return lastHurtByMobTimestamp != this.timestamp && this.canAttack(this.trustedLastHurtBy, this.getTargetConditions()); // Leaf + } + } + +@@ -1032,6 +1037,7 @@ public class Fox extends Animal implements VariantHolder { + + @Override + protected boolean isValidTarget(LevelReader level, BlockPos pos) { ++ // Leaf - Async search block + BlockState blockState = level.getBlockState(pos); + return blockState.is(Blocks.SWEET_BERRY_BUSH) && blockState.getValue(SweetBerryBushBlock.AGE) >= 2 || CaveVines.hasGlowBerries(blockState); + } +@@ -1097,6 +1103,13 @@ public class Fox extends Animal implements VariantHolder { + Fox.this.setSitting(false); + super.start(); + } ++ ++ // Leaf start - Async search block ++ @Override ++ protected TypeToCheck typeToCheck() { ++ return TypeToCheck.FoxEat; ++ } ++ // Leaf end - Async search block + } + + class FoxFloatGoal extends FloatGoal { +diff --git a/net/minecraft/world/entity/animal/Panda.java b/net/minecraft/world/entity/animal/Panda.java +index 0a6a3060f3690ab2d8439d66e6fd6f0c5dc20688..050b060c711ea33d413fba506653618d8c4c4bd4 100644 +--- a/net/minecraft/world/entity/animal/Panda.java ++++ b/net/minecraft/world/entity/animal/Panda.java +@@ -991,31 +991,44 @@ public class Panda extends Animal { + + @Override + public boolean canUse() { ++ // Leaf start - Async look finding ++ if (poll()) { ++ return true; ++ } + if (this.mob.getRandom().nextFloat() >= this.probability) { + return false; +- } else { +- if (this.lookAt == null) { +- ServerLevel serverLevel = getServerLevel(this.mob); +- if (this.lookAtType == Player.class) { +- this.lookAt = serverLevel.getNearestPlayer(this.lookAtContext, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); +- } else { +- this.lookAt = serverLevel.getNearestEntity( +- this.mob +- .level() +- .getEntitiesOfClass( +- this.lookAtType, this.mob.getBoundingBox().inflate(this.lookDistance, 3.0, this.lookDistance), entity -> true +- ), +- this.lookAtContext, +- this.mob, +- this.mob.getX(), +- this.mob.getEyeY(), +- this.mob.getZ() +- ); +- } ++ } ++ if (this.lookAtType == Player.class) { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchPlayer) { ++ getLookAsync(); ++ return false; ++ } ++ } else if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ getLookAsync(); ++ return false; ++ } ++ if (this.lookAt == null) { ++ ServerLevel serverLevel = getServerLevel(this.mob); ++ if (this.lookAtType == Player.class) { ++ this.lookAt = serverLevel.getNearestPlayer(this.lookAtContext, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); ++ } else { ++ this.lookAt = serverLevel.getNearestEntity( ++ this.mob ++ .level() ++ .getEntitiesOfClass( ++ this.lookAtType, this.mob.getBoundingBox().inflate(this.lookDistance, 3.0, this.lookDistance), entity -> true ++ ), ++ this.lookAtContext, ++ this.mob, ++ this.mob.getX(), ++ this.mob.getEyeY(), ++ this.mob.getZ() ++ ); + } +- +- return this.panda.canPerformAction() && this.lookAt != null; + } ++ ++ return this.panda.canPerformAction() && this.lookAt != null; ++ // Leaf end - Async look finding + } + + @Override +diff --git a/net/minecraft/world/entity/animal/Rabbit.java b/net/minecraft/world/entity/animal/Rabbit.java +index da5b32a17283e540615373097acc511d928aeff5..a9cc92d2ae2b1548b27288c190a3f99799dcefbe 100644 +--- a/net/minecraft/world/entity/animal/Rabbit.java ++++ b/net/minecraft/world/entity/animal/Rabbit.java +@@ -642,7 +642,13 @@ public class Rabbit extends Animal implements VariantHolder { + this.wantsToRaid = this.rabbit.wantsMoreFood(); + } + +- return super.canUse(); ++ // Leaf start ++ if (this.wantsToRaid && !this.canRaid) { ++ return super.canUse(); ++ } else { ++ return false; ++ } ++ // Leaf end + } + + @Override +@@ -684,6 +690,7 @@ public class Rabbit extends Animal implements VariantHolder { + + @Override + protected boolean isValidTarget(LevelReader level, BlockPos pos) { ++ // Leaf - Async search block + BlockState blockState = level.getBlockState(pos); + if (blockState.is(Blocks.FARMLAND) && this.wantsToRaid && !this.canRaid) { + blockState = level.getBlockState(pos.above()); +@@ -692,9 +699,17 @@ public class Rabbit extends Animal implements VariantHolder { + return true; + } + } ++ // Leaf - Async search block + + return false; + } ++ ++ // Leaf start - Async search block ++ @Override ++ protected TypeToCheck typeToCheck() { ++ return TypeToCheck.RaidGarden; ++ } ++ // Leaf end - Async search block + } + + public static enum Variant implements StringRepresentable { +diff --git a/net/minecraft/world/entity/animal/Turtle.java b/net/minecraft/world/entity/animal/Turtle.java +index 10477fea8fcd70bf0c1ba4b6e1113625be690e68..65bbfc7d0078465faa6f18ff2f611d813fea310a 100644 +--- a/net/minecraft/world/entity/animal/Turtle.java ++++ b/net/minecraft/world/entity/animal/Turtle.java +@@ -527,8 +527,16 @@ public class Turtle extends Animal { + + @Override + protected boolean isValidTarget(LevelReader level, BlockPos pos) { ++ // Leaf - Async search block + return level.getBlockState(pos).is(Blocks.WATER); + } ++ ++ // Leaf start - Async search block ++ @Override ++ protected TypeToCheck typeToCheck() { ++ return TypeToCheck.TurtleToWater; ++ } ++ // Leaf end - Async search block + } + + static class TurtleLayEggGoal extends MoveToBlockGoal { +@@ -584,8 +592,16 @@ public class Turtle extends Animal { + + @Override + protected boolean isValidTarget(LevelReader level, BlockPos pos) { ++ // Leaf - Async search block + return level.isEmptyBlock(pos.above()) && TurtleEggBlock.isSand(level, pos); + } ++ ++ // Leaf start - Async search block ++ @Override ++ protected TypeToCheck typeToCheck() { ++ return TypeToCheck.TurtleLay; ++ } ++ // Leaf end - Async search block + } + + static class TurtleMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables +diff --git a/net/minecraft/world/entity/animal/Wolf.java b/net/minecraft/world/entity/animal/Wolf.java +index 7cb292de6b27fa4ba3c5fce526a4e939c576789f..856bfeaa91a476c1008909ac35f76dfe0b792c9c 100644 +--- a/net/minecraft/world/entity/animal/Wolf.java ++++ b/net/minecraft/world/entity/animal/Wolf.java +@@ -672,6 +672,7 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder -Date: Sat, 29 Mar 2025 13:40:46 +0100 -Subject: [PATCH] Async target finding - - -diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 24926aa7ed5c78b235659daf18b224b14beb744c..53716dcdb9d3409b7bc71f3064be42bc3f81a86b 100644 ---- a/net/minecraft/server/MinecraftServer.java -+++ b/net/minecraft/server/MinecraftServer.java -@@ -1088,6 +1088,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop players = Lists.newArrayList(); -+ // Leaf start - Async target finding -+ final List players; -+ { -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ this.players = Lists.newCopyOnWriteArrayList(); -+ } else { -+ this.players = Lists.newArrayList(); -+ } -+ } -+ // Leaf end - Async target finding - public final ServerChunkCache chunkSource; - private final MinecraftServer server; - public final net.minecraft.world.level.storage.PrimaryLevelData serverLevelData; // CraftBukkit - type -@@ -218,6 +227,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) - public boolean hasRidableMoveEvent = false; // Purpur - Ridables - final List realPlayers; // Leaves - skip -+ public List asyncAITasks = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); // Leaf - - public LevelChunk getChunkIfLoaded(int x, int z) { - return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately -@@ -861,6 +871,15 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - } - ); - this.tickBlockEntities(); -+ // Leaf start - Async target finding -+ final List tasks = this.asyncAITasks; -+ this.asyncAITasks = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); -+ org.dreeam.leaf.async.ai.AsyncGoalExecutor.EXECUTOR.execute(() -> { -+ for (Runnable asyncTask : tasks) { -+ asyncTask.run(); -+ } -+ }); -+ // Leaf end - Async target finding - } - - // Paper - rewrite chunk system -diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 075fcbcde23b5bb7b27ff622e8d188c3a2583973..e5aa7c542b1e9c9362aa1feeeebef45919feac70 100644 ---- a/net/minecraft/world/entity/Entity.java -+++ b/net/minecraft/world/entity/Entity.java -@@ -243,6 +243,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - protected Vec3 stuckSpeedMultiplier = Vec3.ZERO; - @Nullable - private Entity.RemovalReason removalReason; -+ private final java.util.concurrent.atomic.AtomicBoolean isRemoved = new java.util.concurrent.atomic.AtomicBoolean(false); // Leaf - atomic removal check - public static final float DEFAULT_BB_WIDTH = 0.6F; - public static final float DEFAULT_BB_HEIGHT = 1.8F; - public float moveDist; -@@ -5028,7 +5029,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - public final boolean isRemoved() { -- return this.removalReason != null; -+ // Leaf start - atomic removal check -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ return this.isRemoved.getAcquire(); -+ } else { -+ return this.removalReason != null; -+ } -+ // Leaf end - atomic removal check - } - - @Nullable -@@ -5055,6 +5062,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers - if (this.removalReason == null) { - this.removalReason = removalReason; -+ // Leaf start - atomic removal check -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ this.isRemoved.setRelease(true); -+ } -+ // Leaf end - atomic removal check - } - - if (this.removalReason.shouldDestroy()) { -@@ -5074,6 +5086,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - - public void unsetRemoved() { - this.removalReason = null; -+ // Leaf start - atomic removal check -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ this.isRemoved.setRelease(false); -+ } -+ // Leaf end - atomic removal check - } - - // Paper start - Folia schedulers -diff --git a/net/minecraft/world/entity/ai/goal/GoalSelector.java b/net/minecraft/world/entity/ai/goal/GoalSelector.java -index e82e32407cec6109b9c3b0106295217f4a3f4aa2..d7361d2f0a777f745133c3687637ba6ab451af5b 100644 ---- a/net/minecraft/world/entity/ai/goal/GoalSelector.java -+++ b/net/minecraft/world/entity/ai/goal/GoalSelector.java -@@ -22,17 +22,30 @@ public class GoalSelector { - }; - private final Map lockedFlags = new EnumMap<>(Goal.Flag.class); - private final Set availableGoals = new ObjectLinkedOpenHashSet<>(); -+ private final java.util.List asyncGoals = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); // Leaf - private static final Goal.Flag[] GOAL_FLAG_VALUES = Goal.Flag.values(); // Paper - remove streams from GoalSelector - private final ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet goalTypes = new ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from GoalSelector - private int curRate; // Paper - EAR 2 - - public void addGoal(int priority, Goal goal) { -- this.availableGoals.add(new WrappedGoal(priority, goal)); -+ // Leaf start -+ WrappedGoal wrapped = new WrappedGoal(priority, goal); -+ if (this.availableGoals.add(wrapped) -+ && org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled -+ && goal instanceof org.dreeam.leaf.async.ai.AsyncGoal) { -+ asyncGoals.add(wrapped); -+ } -+ // Leaf end - } - - @VisibleForTesting - public void removeAllGoals(Predicate filter) { - this.availableGoals.removeIf(wrappedGoal -> filter.test(wrappedGoal.getGoal())); -+ // Leaf start -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ this.asyncGoals.removeIf(wrappedGoal -> filter.test(wrappedGoal.getGoal())); -+ } -+ // Leaf end - } - - // Paper start - EAR 2 -@@ -41,8 +54,37 @@ public class GoalSelector { - tickRate = Math.min(tickRate, 3); // Dreeam TODO - Waiting Paper - this.curRate++; - //return this.curRate % 3 == 0; // TODO newGoalRate was already unused in 1.20.4, check if this is correct -- return this.curRate % tickRate == 0; -+ // Leaf start -+ boolean willTick = this.curRate % tickRate == 0; -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ for (var wrappedGoal : this.asyncGoals) { -+ if (wrappedGoal.getGoal() instanceof org.dreeam.leaf.async.ai.AsyncGoal asyncGoal -+ && asyncGoal.poll() -+ && !willTick) { -+ if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.goalTypes) || !wrappedGoal.canContinueToUse())) { -+ wrappedGoal.stop(); -+ } -+ if (!wrappedGoal.isRunning() && !goalContainsAnyFlags(wrappedGoal, this.goalTypes) && goalCanBeReplacedForAllFlags(wrappedGoal, this.lockedFlags) && wrappedGoal.canUse()) { -+ long flagIterator = wrappedGoal.getFlags().getBackingSet(); -+ int wrappedGoalSize = wrappedGoal.getFlags().size(); -+ for (int i = 0; i < wrappedGoalSize; ++i) { -+ final Goal.Flag flag = GOAL_FLAG_VALUES[Long.numberOfTrailingZeros(flagIterator)]; -+ flagIterator ^= ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit(flagIterator); -+ // Paper end -+ WrappedGoal wrappedGoal1 = this.lockedFlags.getOrDefault(flag, NO_GOAL); -+ wrappedGoal1.stop(); -+ this.lockedFlags.put(flag, wrappedGoal); -+ } -+ -+ wrappedGoal.start(); -+ // wrappedGoal.tick(); -+ } -+ } -+ } -+ } -+ return willTick; - // Pufferfish end -+ // Leaf end - } - - public boolean hasTasks() { -@@ -63,6 +105,11 @@ public class GoalSelector { - } - - this.availableGoals.removeIf(wrappedGoal1 -> wrappedGoal1.getGoal() == goal); -+ // Leaf start -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ this.asyncGoals.removeIf(wrappedGoal1 -> wrappedGoal1.getGoal() == goal); -+ } -+ // Leaf end - } - - // Paper start - Perf: optimize goal types -diff --git a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -index 41ee3cdc45ecc8376a2203ed588bb544ed377294..49e836a012f0db9dec6578caa341a50e703ee98e 100644 ---- a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -+++ b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -@@ -11,13 +11,20 @@ import net.minecraft.world.entity.ai.targeting.TargetingConditions; - import net.minecraft.world.entity.player.Player; - import net.minecraft.world.phys.AABB; - --public class NearestAttackableTargetGoal extends TargetGoal { -+public class NearestAttackableTargetGoal extends TargetGoal implements org.dreeam.leaf.async.ai.AsyncGoal { // Leaf - private static final int DEFAULT_RANDOM_INTERVAL = 10; - protected final Class targetType; - protected final int randomInterval; - @Nullable - protected LivingEntity target; -- protected TargetingConditions targetConditions; -+ @Nullable protected TargetingConditions.Selector selector; // Leaf - create TargetingConditions instead of reusing it -+ // Leaf start - Async Target Finding -+ private static final org.apache.logging.log4j.Logger LOGGER = org.apache.logging.log4j.LogManager.getLogger("Leaf Async Target Lookup"); -+ // Flag to track if a search is in progress -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); -+ protected boolean readyNow = false; -+ // Leaf end - Async Target Finding - - public NearestAttackableTargetGoal(Mob mob, Class targetType, boolean mustSee) { - this(mob, targetType, 10, mustSee, false, null); -@@ -38,11 +45,17 @@ public class NearestAttackableTargetGoal extends TargetG - this.targetType = targetType; - this.randomInterval = reducedTickDelay(interval); - this.setFlags(EnumSet.of(Goal.Flag.TARGET)); -- this.targetConditions = TargetingConditions.forCombat().range(this.getFollowDistance()).selector(selector); -+ this.selector = selector; // Leaf - } - - @Override - public boolean canUse() { -+ // Leaf start - Async target finding -+ if (readyNow) { -+ readyNow = false; -+ return true; -+ } -+ // Leaf end - Async target finding - if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) { - return false; - } else { -@@ -55,8 +68,37 @@ public class NearestAttackableTargetGoal extends TargetG - return this.mob.getBoundingBox().inflate(targetDistance, targetDistance, targetDistance); - } - -+ // Leaf start - Async target finding -+ @Override -+ public boolean poll() { -+ LivingEntity t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return false; -+ } -+ ServerLevel serverLevel = getServerLevel(this.mob); -+ if (serverLevel == null || !t.isAlive() || !this.getTargetConditions().test(serverLevel, this.mob, t)) { -+ return false; -+ } -+ this.target = t; -+ if (this.readyNow) { -+ LOGGER.warn("NearestAttackableTargetGoal#findTarget call twice"); -+ } -+ this.readyNow = true; -+ return true; -+ } -+ - protected void findTarget() { - ServerLevel serverLevel = getServerLevel(this.mob); -+ -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ this.findTargetAsync(); -+ if (serverLevel == null || this.target == null || !this.target.isAlive() || !this.getTargetConditions().test(serverLevel, this.mob, this.target)) { -+ this.target = null; -+ return; -+ } -+ return; -+ } -+ - if (this.targetType != Player.class && this.targetType != ServerPlayer.class) { - this.target = serverLevel.getNearestEntity( - this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true), -@@ -71,6 +113,156 @@ public class NearestAttackableTargetGoal extends TargetG - } - } - -+ private void findTargetAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ -+ // Capture mutable state to avoid race conditions -+ final Mob mob = this.mob; -+ -+ // Safety check -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ -+ final double x = mob.getX(); -+ final double y = mob.getEyeY(); -+ final double z = mob.getZ(); -+ final double followDistance = this.getFollowDistance(); -+ final TargetingConditions targetConditions = this.getTargetConditions(); -+ final Class targetType = this.targetType; -+ final double maxDistSqr = followDistance * followDistance; -+ final AABB targetSearch = getTargetSearchArea(this.getFollowDistance()); -+ final ServerLevel serverLevel = getServerLevel(mob); -+ -+ serverLevel.asyncAITasks.add(() -> { -+ try { -+ if (mob.level() == null || mob.level() != serverLevel) { -+ return; -+ } -+ if (mob.isRemoved() || !mob.isAlive()) { -+ return; -+ } -+ -+ if (targetType != Player.class && targetType != ServerPlayer.class) { -+ java.util.List entities; -+ try { -+ entities = mob.level().getEntitiesOfClass(targetType, targetSearch, entity -> entity != null && entity != mob && !entity.isRemoved() && entity.isAlive()); -+ } catch (Exception e) { -+ LOGGER.warn("Exception getting entities", e); -+ return; -+ } -+ -+ if (entities != null && !entities.isEmpty()) { -+ var result = findNearestEntitySafely(entities, targetConditions, mob, x, y, z, serverLevel, maxDistSqr); -+ pendingTarget.setRelease(result); -+ } -+ } else { -+ var result = findNearestPlayerSafely(targetConditions, mob, x, y, z, serverLevel); -+ pendingTarget.setRelease(result); -+ } -+ } catch (Exception e) { -+ LOGGER.warn("Exception during async target finding", e); -+ } finally { -+ isSearching.setRelease(false); -+ } -+ }); -+ } -+ -+ @Nullable -+ private static LivingEntity findNearestEntitySafely( -+ java.util.List entities, -+ TargetingConditions conditions, -+ Mob source, -+ double x, -+ double y, -+ double z, -+ ServerLevel level, -+ double maxDistSqr) { -+ -+ if (entities == null || entities.isEmpty() || level == null) { -+ return null; -+ } -+ -+ try { -+ double closestDistSq = maxDistSqr; -+ LivingEntity closest = null; -+ -+ for (LivingEntity entity : entities) { -+ if (entity == null || entity == source || entity.isRemoved() || !entity.isAlive()) { -+ continue; -+ } -+ -+ if (conditions.test(level, source, entity)) { -+ double dx = entity.getX() - x; -+ double dy = entity.getY() - y; -+ double dz = entity.getZ() - z; -+ double distSq = dx * dx + dy * dy + dz * dz; -+ -+ if (distSq < closestDistSq) { -+ closestDistSq = distSq; -+ closest = entity; -+ } -+ } -+ } -+ -+ return closest; -+ } catch (Exception e) { -+ LOGGER.error("Exception in findNearestEntitySafely", e); -+ return null; -+ } -+ } -+ -+ @Nullable -+ private static Player findNearestPlayerSafely( -+ TargetingConditions conditions, -+ Mob source, -+ double x, -+ double y, -+ double z, -+ ServerLevel level) { -+ -+ if (level == null) { -+ return null; -+ } -+ -+ try { -+ java.util.List players = level.players(); -+ if (players == null || players.isEmpty()) { -+ return null; -+ } -+ -+ double closestDistSq = -1.0; -+ Player closest = null; -+ -+ for (Player player : players) { -+ if (player == null || player.isRemoved() || !player.isAlive()) { -+ continue; -+ } -+ -+ if (conditions.test(level, source, player)) { -+ double dx = player.getX() - x; -+ double dy = player.getY() - y; -+ double dz = player.getZ() - z; -+ double distSq = dx * dx + dy * dy + dz * dz; -+ -+ if (closestDistSq == -1.0 || distSq < closestDistSq) { -+ closestDistSq = distSq; -+ closest = player; -+ } -+ } -+ } -+ -+ return closest; -+ } catch (Exception e) { -+ LOGGER.error("Exception in findNearestPlayerSafely", e); -+ return null; -+ } -+ } -+ // Leaf end - Async target finding -+ - @Override - public void start() { - this.mob.setTarget(this.target, this.target instanceof ServerPlayer ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY, true); // CraftBukkit - reason -@@ -81,7 +273,9 @@ public class NearestAttackableTargetGoal extends TargetG - this.target = target; - } - -- private TargetingConditions getTargetConditions() { -- return this.targetConditions.range(this.getFollowDistance()); -+ // Leaf start -+ protected TargetingConditions getTargetConditions() { -+ return TargetingConditions.forCombat().range(this.getFollowDistance()).selector(this.selector); - } -+ // Leaf end - } -diff --git a/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java -index 4604a603c4ddd0a9242e859aaa5a511c2d4c4f84..af9101bb0639dedee41e2e3e97e05cad91ec42e7 100644 ---- a/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java -+++ b/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java -@@ -10,7 +10,7 @@ public class NearestHealableRaiderTargetGoal extends Nea - private int cooldown = 0; - - public NearestHealableRaiderTargetGoal(Raider raider, Class targetType, boolean mustSee, @Nullable TargetingConditions.Selector selector) { -- super(raider, targetType, 500, mustSee, false, selector); -+ super(raider, targetType, 100, mustSee, false, selector); // Leaf 500 -> 100 seem doesn't used before - } - - public int getCooldown() { -@@ -23,6 +23,16 @@ public class NearestHealableRaiderTargetGoal extends Nea - - @Override - public boolean canUse() { -+ // Leaf start - Async target finding -+ if (readyNow) { -+ readyNow = false; -+ return ((Raider) this.mob).hasActiveRaid(); -+ } -+ // 5 sec -+ if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) { -+ return false; -+ } -+ // Leaf end - Async target finding - if (this.cooldown > 0 || !this.mob.getRandom().nextBoolean()) { - return false; - } else if (!((Raider)this.mob).hasActiveRaid()) { -diff --git a/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java -index abf57494950f55bbd75f335f26736cb9e703c197..683722fbd07fcae9cdd72928ed25fd084202b57e 100644 ---- a/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java -+++ b/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java -@@ -20,6 +20,15 @@ public class NonTameRandomTargetGoal extends NearestAtta - - @Override - public boolean canContinueToUse() { -- return this.targetConditions != null ? this.targetConditions.test(getServerLevel(this.mob), this.mob, this.target) : super.canContinueToUse(); -+ // Leaf start -+ if (this.target == null || !this.target.isAlive() || this.target.isRemoved()) { -+ return false; -+ } -+ var serverLevel = getServerLevel(this.mob); -+ if (serverLevel == null) { -+ return false; -+ } -+ return this.getTargetConditions().test(serverLevel, this.mob, this.target) && super.canContinueToUse(); -+ // Leaf end - } - } -diff --git a/net/minecraft/world/entity/ai/sensing/Sensing.java b/net/minecraft/world/entity/ai/sensing/Sensing.java -index 002d3c0d8b1107a275020d5c582c37e9a5c536ee..3f8c18f4040f4929df79ba85906330b150720cb0 100644 ---- a/net/minecraft/world/entity/ai/sensing/Sensing.java -+++ b/net/minecraft/world/entity/ai/sensing/Sensing.java -@@ -32,9 +32,21 @@ public class Sensing { - // Gale end - Petal - reduce line of sight updates - expiring entity id lists - } - -+ // Leaf start - async target finding - public void tick() { -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ synchronized (this) { -+ tick1(); -+ } -+ } else { -+ tick1(); -+ } -+ } -+ -+ private void tick1() { -+ // Leaf end - async target finding - if (this.expiring == null) { // Gale - Petal - reduce line of sight updates -- this.seen.clear(); -+ this.seen.clear(); - // Gale start - Petal - reduce line of sight updates - } else { - var expiringNow = this.expiring[this.nextToExpireIndex]; -@@ -62,7 +74,19 @@ public class Sensing { - // Gale end - Petal - reduce line of sight updates - } - -+ // Leaf start - async target finding - public boolean hasLineOfSight(Entity entity) { -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ synchronized (this) { -+ return hasLineOfSight1(entity); -+ } -+ } else { -+ return hasLineOfSight1(entity); -+ } -+ } -+ -+ private boolean hasLineOfSight1(Entity entity) { -+ // Leaf end - async target finding - int id = entity.getId(); - // Gale start - Petal - reduce line of sight cache lookups - merge sets - int cached = this.seen.get(id); -diff --git a/net/minecraft/world/entity/animal/Fox.java b/net/minecraft/world/entity/animal/Fox.java -index 90452f0945e761077608692877677f522d38bccd..28dbba5494779d82b6ae7c435db0ed76dc5eaf1f 100644 ---- a/net/minecraft/world/entity/animal/Fox.java -+++ b/net/minecraft/world/entity/animal/Fox.java -@@ -849,13 +849,18 @@ public class Fox extends Animal implements VariantHolder { - return false; - } else { - ServerLevel serverLevel = getServerLevel(Fox.this.level()); -+ // Leaf start -+ if (serverLevel == null) { -+ return false; -+ } -+ // Leaf end - - for (UUID uuid : Fox.this.getTrustedUUIDs()) { - if (serverLevel.getEntity(uuid) instanceof LivingEntity livingEntity) { - this.trustedLastHurt = livingEntity; - this.trustedLastHurtBy = livingEntity.getLastHurtByMob(); - int lastHurtByMobTimestamp = livingEntity.getLastHurtByMobTimestamp(); -- return lastHurtByMobTimestamp != this.timestamp && this.canAttack(this.trustedLastHurtBy, this.targetConditions); -+ return lastHurtByMobTimestamp != this.timestamp && this.canAttack(this.trustedLastHurtBy, this.getTargetConditions()); // Leaf - } - } - -@@ -863,6 +868,13 @@ public class Fox extends Animal implements VariantHolder { - } - } - -+ // Leaf start - Async target finding -+ @Override -+ public boolean poll() { -+ return false; -+ } -+ // Leaf end - Async target finding -+ - @Override - public void start() { - this.setTarget(this.trustedLastHurtBy); -diff --git a/net/minecraft/world/entity/monster/EnderMan.java b/net/minecraft/world/entity/monster/EnderMan.java -index c7897532163d4fdf5a82982f7d24a47dd61e3dfa..914d171c0183f00ff474517e2fa3ec00d7f1d6f1 100644 ---- a/net/minecraft/world/entity/monster/EnderMan.java -+++ b/net/minecraft/world/entity/monster/EnderMan.java -@@ -608,6 +608,13 @@ public class EnderMan extends Monster implements NeutralMob { - this.enderman.setBeingStaredAt(); - } - -+ // Leaf start - Async target finding -+ @Override -+ public boolean poll() { -+ return false; -+ } -+ // Leaf end - Async target finding -+ - @Override - public void stop() { - this.pendingTarget = null; diff --git a/leaf-server/minecraft-patches/features/0156-Optimize-ThreadedTicketLevelPropagator.patch b/leaf-server/minecraft-patches/features/0155-Optimize-ThreadedTicketLevelPropagator.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0156-Optimize-ThreadedTicketLevelPropagator.patch rename to leaf-server/minecraft-patches/features/0155-Optimize-ThreadedTicketLevelPropagator.patch diff --git a/leaf-server/minecraft-patches/features/0157-Optimise-MobEffectUtil-getDigSpeedAmplification.patch b/leaf-server/minecraft-patches/features/0156-Optimise-MobEffectUtil-getDigSpeedAmplification.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0157-Optimise-MobEffectUtil-getDigSpeedAmplification.patch rename to leaf-server/minecraft-patches/features/0156-Optimise-MobEffectUtil-getDigSpeedAmplification.patch diff --git a/leaf-server/minecraft-patches/features/0158-Optimise-chunkUnloads.patch b/leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0158-Optimise-chunkUnloads.patch rename to leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoal.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoal.java deleted file mode 100644 index 41651921..00000000 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoal.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.dreeam.leaf.async.ai; - -public interface AsyncGoal { - boolean poll(); -} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java index df7feb76..45e9aba3 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java @@ -1,19 +1,18 @@ package org.dreeam.leaf.async.ai; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import org.jetbrains.annotations.Nullable; + +import java.util.List; public class AsyncGoalExecutor { - public static final java.util.concurrent.ExecutorService EXECUTOR = new ThreadPoolExecutor( - 1, - 1, - 0L, - TimeUnit.MILLISECONDS, - new ArrayBlockingQueue<>(128), - new com.google.common.util.concurrent.ThreadFactoryBuilder() - .setNameFormat("Leaf Async Target Finding Thread") - .setDaemon(true) - .setPriority(Thread.NORM_PRIORITY - 2) - .build(), new ThreadPoolExecutor.CallerRunsPolicy()); + @Nullable + public static java.util.concurrent.ExecutorService EXECUTOR; + + public static final org.apache.logging.log4j.Logger LOGGER = org.apache.logging.log4j.LogManager.getLogger("Leaf Async Entity Lookup"); + + public static void runTasks(List tasks) { + for (Runnable task : tasks) { + task.run(); + } + } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncBlockFinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncBlockFinding.java deleted file mode 100644 index fffd4b3e..00000000 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncBlockFinding.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.dreeam.leaf.config.modules.async; - -import org.dreeam.leaf.config.ConfigModules; -import org.dreeam.leaf.config.EnumConfigCategory; - -public class AsyncBlockFinding extends ConfigModules { - - public String getBasePath() { - return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-block-finding"; - } - - public static boolean enabled = false; - public static boolean asyncBlockFindingInitialized; - - @Override - public void onLoaded() { - config.addCommentRegionBased(getBasePath(), """ - This moves the expensive search calculations to a background thread while - keeping the actual block validation on the main thread.""", - """ - 这会将昂贵的搜索计算移至后台线程, 同时在主线程上保持实际的方块验证."""); - - if (!asyncBlockFindingInitialized) { - asyncBlockFindingInitialized = true; - enabled = config.getBoolean(getBasePath() + ".enabled", enabled); - } - } -} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java index 961572c5..532786ba 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java @@ -1,10 +1,15 @@ package org.dreeam.leaf.config.modules.async; +import org.dreeam.leaf.async.ai.AsyncGoalExecutor; import org.dreeam.leaf.config.ConfigModules; import org.dreeam.leaf.config.EnumConfigCategory; import org.dreeam.leaf.config.annotations.Experimental; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + public class AsyncTargetFinding extends ConfigModules { public String getBasePath() { @@ -13,6 +18,11 @@ public class AsyncTargetFinding extends ConfigModules { @Experimental public static boolean enabled = false; + public static boolean alertOther = true; + public static boolean searchBlock = false; + public static boolean searchEntity = true; + public static boolean searchPlayer = false; + public static boolean searchPlayerTempt = false; public static boolean asyncTargetFindingInitialized; @Override @@ -24,9 +34,35 @@ public class AsyncTargetFinding extends ConfigModules { """ 这会将昂贵的实体目标搜索计算移至后台线程, 同时在主线程上保持实际的实体验证."""); - if (!asyncTargetFindingInitialized) { - asyncTargetFindingInitialized = true; - enabled = config.getBoolean(getBasePath() + ".enabled", enabled); + if (asyncTargetFindingInitialized) { + return; } + asyncTargetFindingInitialized = true; + enabled = config.getBoolean(getBasePath() + ".enabled", enabled); + alertOther = config.getBoolean(getBasePath() + ".async-alert-other", true); + searchBlock = config.getBoolean(getBasePath() + ".async-search-block", false); + searchEntity = config.getBoolean(getBasePath() + ".async-search-entity", true); + searchPlayer = config.getBoolean(getBasePath() + ".async-search-player", false); + searchPlayerTempt = config.getBoolean(getBasePath() + ".async-search-player-tempt", false); + if (!enabled) { + alertOther = false; + searchEntity = false; + searchBlock = false; + searchPlayer = false; + searchPlayerTempt = false; + return; + } + AsyncGoalExecutor.EXECUTOR = new ThreadPoolExecutor( + 1, + 1, + 0L, + TimeUnit.MILLISECONDS, + new ArrayBlockingQueue<>(128), + new com.google.common.util.concurrent.ThreadFactoryBuilder() + .setNameFormat("Leaf Async Target Finding Thread") + .setDaemon(true) + .setPriority(Thread.NORM_PRIORITY - 2) + .build(), + new ThreadPoolExecutor.CallerRunsPolicy()); } } From 0bd14537d93760c67dea1dcddb5bace8e0b2771f Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Fri, 25 Apr 2025 17:10:50 +0800 Subject: [PATCH 15/45] optimize BlockEntityType#isValid (#290) --- ...159-optimize-BlockEntityType-isValid.patch | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 leaf-server/minecraft-patches/features/0159-optimize-BlockEntityType-isValid.patch diff --git a/leaf-server/minecraft-patches/features/0159-optimize-BlockEntityType-isValid.patch b/leaf-server/minecraft-patches/features/0159-optimize-BlockEntityType-isValid.patch new file mode 100644 index 00000000..af50473a --- /dev/null +++ b/leaf-server/minecraft-patches/features/0159-optimize-BlockEntityType-isValid.patch @@ -0,0 +1,53 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Sat, 19 Apr 2025 22:12:19 +0800 +Subject: [PATCH] optimize BlockEntityType#isValid + + +diff --git a/net/minecraft/world/level/block/entity/BlockEntityType.java b/net/minecraft/world/level/block/entity/BlockEntityType.java +index a2a674f18d7f2c2e50a6b25f9ac3bf4534275976..bcfd5dc20d20fe6b74d726c7086a0cc7ae2565de 100644 +--- a/net/minecraft/world/level/block/entity/BlockEntityType.java ++++ b/net/minecraft/world/level/block/entity/BlockEntityType.java +@@ -246,12 +246,20 @@ public class BlockEntityType { + } + + Util.fetchChoiceType(References.BLOCK_ENTITY, name); +- return Registry.register(BuiltInRegistries.BLOCK_ENTITY_TYPE, name, new BlockEntityType<>(factory, Set.of(validBlocks))); ++ return Registry.register(BuiltInRegistries.BLOCK_ENTITY_TYPE, name, new BlockEntityType<>(factory, com.google.common.collect.ImmutableSet.copyOf(validBlocks))); // Leaf - ensure immutable + } + + private BlockEntityType(BlockEntityType.BlockEntitySupplier factory, Set validBlocks) { + this.factory = factory; + this.validBlocks = validBlocks; ++ // Leaf start ++ for (Block validBlock : validBlocks) { ++ if (validBlock.blockEntityType != null) { ++ throw new IllegalStateException("Duplicate block entity type"); ++ } ++ validBlock.blockEntityType = this; ++ } ++ // Leaf end + } + + @Nullable +@@ -260,7 +268,7 @@ public class BlockEntityType { + } + + public boolean isValid(BlockState state) { +- return this.validBlocks.contains(state.getBlock()); ++ return state.getBlock().blockEntityType == this; // Leaf - remove hash lookup + } + + @Deprecated +diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java +index d35211b0cae66b1a40e89539507e55973313f46f..fa6d4217f222e8d9decde18e967e951dd833a1c7 100644 +--- a/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -100,6 +100,7 @@ public abstract class BlockBehaviour implements FeatureElement { + protected final BlockBehaviour.Properties properties; + protected final Optional> drops; + protected final String descriptionId; ++ @Nullable public net.minecraft.world.level.block.entity.BlockEntityType blockEntityType = null; // Leaf + + public BlockBehaviour(BlockBehaviour.Properties properties) { + this.hasCollision = properties.hasCollision; From efe2191b1f1f2d9e38a46597a336d3cf940495ec Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Fri, 25 Apr 2025 11:46:55 -0400 Subject: [PATCH 16/45] Fix plugin compatibility --- leaf-api/build.gradle.kts.patch | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/leaf-api/build.gradle.kts.patch b/leaf-api/build.gradle.kts.patch index ed47c8b0..4cc2450c 100644 --- a/leaf-api/build.gradle.kts.patch +++ b/leaf-api/build.gradle.kts.patch @@ -17,16 +17,17 @@ val apiAndDocs: Configuration by configurations.creating { attributes { -@@ -41,9 +_,11 @@ +@@ -41,9 +_,13 @@ dependencies { // api dependencies are listed transitively to API consumers - api("com.google.guava:guava:33.3.1-jre") -- api("com.google.code.gson:gson:2.11.0") -- api("org.yaml:snakeyaml:2.2") + // Leaf start - Bump Dependencies + api("com.google.guava:guava:33.4.0-jre") -+ api("com.google.code.gson:gson:2.12.1") ++ // Waiting Paper, Gson has breaking change since 2.12.0 ++ // See https://github.com/google/gson/commit/6c2e3db7d25ceceabe056aeb8b65477fdd509214 + api("com.google.code.gson:gson:2.11.0") +- api("org.yaml:snakeyaml:2.2") + api("org.yaml:snakeyaml:2.3") // 2.4 removed `org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder` + // Leaf end - Bump Dependencies api("org.joml:joml:1.10.8") { From 28543d51ddc3dd2008887cb67d4ffab814d93c12 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Sat, 26 Apr 2025 00:01:52 +0800 Subject: [PATCH 17/45] Fix some async config disappear after reload (#298) * fix async config disappear * fix comment of AsyncPathfinding config --- .../modules/async/AsyncMobSpawning.java | 11 +++++--- .../modules/async/AsyncPathfinding.java | 28 ++++++++++++------- .../modules/async/AsyncTargetFinding.java | 7 ++++- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java index 931e5831..21e21882 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java @@ -10,7 +10,7 @@ public class AsyncMobSpawning extends ConfigModules { } public static boolean enabled = true; - public static boolean asyncMobSpawningInitialized; + private static boolean asyncMobSpawningInitialized; @Override public void onLoaded() { @@ -26,9 +26,12 @@ public class AsyncMobSpawning extends ConfigModules { 须在Paper配置文件中打开 per-player-mob-spawns 才能生效."""); // This prevents us from changing the value during a reload. - if (!asyncMobSpawningInitialized) { - asyncMobSpawningInitialized = true; - enabled = config.getBoolean(getBasePath() + ".enabled", enabled); + if (asyncMobSpawningInitialized) { + config.getConfigSection(getBasePath()); + return; } + asyncMobSpawningInitialized = true; + + enabled = config.getBoolean(getBasePath() + ".enabled", enabled); } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java index 1792a970..6109e12d 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java @@ -16,9 +16,26 @@ public class AsyncPathfinding extends ConfigModules { public static int asyncPathfindingKeepalive = 60; public static int asyncPathfindingQueueSize = 0; public static PathfindTaskRejectPolicy asyncPathfindingRejectPolicy = PathfindTaskRejectPolicy.FLUSH_ALL; + private static boolean asyncPathfindingInitialized; @Override public void onLoaded() { + config.addCommentRegionBased(getBasePath() + ".reject-policy", + """ + The policy to use when the queue is full and a new task is submitted. + FLUSH_ALL: All pending tasks will be run on server thread. + CALLER_RUNS: Newly submitted task will be run on server thread.""", + """ + 当队列满时, 新提交的任务将使用以下策略处理. + FLUSH_ALL: 所有等待中的任务都将在主线程上运行. + CALLER_RUNS: 新提交的任务将在主线程上运行.""" + ); + if (asyncPathfindingInitialized) { + config.getConfigSection(getBasePath()); + return; + } + asyncPathfindingInitialized = true; + final int availableProcessors = Runtime.getRuntime().availableProcessors(); enabled = config.getBoolean(getBasePath() + ".enabled", enabled); asyncPathfindingMaxThreads = config.getInt(getBasePath() + ".max-threads", asyncPathfindingMaxThreads); @@ -37,15 +54,6 @@ public class AsyncPathfinding extends ConfigModules { if (asyncPathfindingQueueSize <= 0) asyncPathfindingQueueSize = asyncPathfindingMaxThreads * 256; - asyncPathfindingRejectPolicy = PathfindTaskRejectPolicy.fromString(config.getString(getBasePath() + ".reject-policy", availableProcessors >= 12 && asyncPathfindingQueueSize < 512 ? PathfindTaskRejectPolicy.FLUSH_ALL.toString() : PathfindTaskRejectPolicy.CALLER_RUNS.toString(), config.pickStringRegionBased( - """ - The policy to use when the queue is full and a new task is submitted. - FLUSH_ALL: All pending tasks will be run on server thread. - CALLER_RUNS: Newly submitted task will be run on server thread.""", - """ - 当队列满时, 新提交的任务将使用以下策略处理. - FLUSH_ALL: 所有等待中的任务都将在主线程上运行. - CALLER_RUNS: 新提交的任务将在主线程上运行.""" - ))); + asyncPathfindingRejectPolicy = PathfindTaskRejectPolicy.fromString(config.getString(getBasePath() + ".reject-policy", availableProcessors >= 12 && asyncPathfindingQueueSize < 512 ? PathfindTaskRejectPolicy.FLUSH_ALL.toString() : PathfindTaskRejectPolicy.CALLER_RUNS.toString())); } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java index 532786ba..822f142f 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java @@ -4,6 +4,7 @@ package org.dreeam.leaf.config.modules.async; import org.dreeam.leaf.async.ai.AsyncGoalExecutor; import org.dreeam.leaf.config.ConfigModules; import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.LeafConfig; import org.dreeam.leaf.config.annotations.Experimental; import java.util.concurrent.ArrayBlockingQueue; @@ -23,7 +24,7 @@ public class AsyncTargetFinding extends ConfigModules { public static boolean searchEntity = true; public static boolean searchPlayer = false; public static boolean searchPlayerTempt = false; - public static boolean asyncTargetFindingInitialized; + private static boolean asyncTargetFindingInitialized; @Override public void onLoaded() { @@ -32,12 +33,15 @@ public class AsyncTargetFinding extends ConfigModules { This moves the expensive entity target search calculations to a background thread while keeping the actual entity validation on the main thread.""", """ + **实验性功能** 这会将昂贵的实体目标搜索计算移至后台线程, 同时在主线程上保持实际的实体验证."""); if (asyncTargetFindingInitialized) { + config.getConfigSection(getBasePath()); return; } asyncTargetFindingInitialized = true; + enabled = config.getBoolean(getBasePath() + ".enabled", enabled); alertOther = config.getBoolean(getBasePath() + ".async-alert-other", true); searchBlock = config.getBoolean(getBasePath() + ".async-search-block", false); @@ -64,5 +68,6 @@ public class AsyncTargetFinding extends ConfigModules { .setPriority(Thread.NORM_PRIORITY - 2) .build(), new ThreadPoolExecutor.CallerRunsPolicy()); + LeafConfig.LOGGER.info("Using 1 threads for Async Target Finding"); } } From 1fa4df017fcbf96053778d2be3dfe9f7aaa2ab20 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Fri, 25 Apr 2025 12:04:54 -0400 Subject: [PATCH 18/45] Makes Pascalpex happy --- .../java/org/dreeam/leaf/config/modules/misc/ServerBrand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/ServerBrand.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/ServerBrand.java index 6e20b63f..d0719625 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/ServerBrand.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/ServerBrand.java @@ -10,7 +10,7 @@ public class ServerBrand extends ConfigModules { } public static String serverModName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); - public static String serverGUIName = "Leaf Console"; + public static String serverGUIName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName() + " Console"; @Override public void onLoaded() { From 08ca77097cc68c1498df3ea5b1e1f915bf459464 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Sat, 26 Apr 2025 00:42:37 +0800 Subject: [PATCH 19/45] Temporary disable async target finding when PWT enabled --- .../features/0154-Async-target-finding.patch | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch index 7b8c239d..7c36b574 100644 --- a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch @@ -25,7 +25,7 @@ index 24926aa7ed5c78b235659daf18b224b14beb744c..2603f5ca5e5f3fd86af76aec7e16039b public String getLocalIp() { diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 90bdcd168ad5b1a940f81b191bd59a34d3a33070..252591f0192f8cb5b2bdbb51625e50ae631301ad 100644 +index 90bdcd168ad5b1a940f81b191bd59a34d3a33070..051bf2ec9c312460b1379cea0f8a2a3adffcd254 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -177,7 +177,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -54,11 +54,18 @@ index 90bdcd168ad5b1a940f81b191bd59a34d3a33070..252591f0192f8cb5b2bdbb51625e50ae public LevelChunk getChunkIfLoaded(int x, int z) { return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately -@@ -861,6 +871,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -861,6 +871,21 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } ); this.tickBlockEntities(); + // Leaf start - Async target finding ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled ++ && org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ++ ) { ++ final var tasks = this.asyncAITasks; ++ this.asyncAITasks = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.runTasks(tasks); ++ } else + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { + final var tasks = this.asyncAITasks; + this.asyncAITasks = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); From 1a693822bc96cc62fbfd9759a018860f72245c41 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Fri, 25 Apr 2025 13:06:39 -0400 Subject: [PATCH 20/45] [ci skip] Split publish api workflow --- .github/workflows/build-1214.yml | 9 ------ .github/workflows/build-pr.yml | 9 +++--- .github/workflows/publish-api.yml | 54 +++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/publish-api.yml diff --git a/.github/workflows/build-1214.yml b/.github/workflows/build-1214.yml index 24e62557..e19260f2 100644 --- a/.github/workflows/build-1214.yml +++ b/.github/workflows/build-1214.yml @@ -82,15 +82,6 @@ jobs: - name: Create MojmapPaperclipJar run: ./gradlew createMojmapPaperclipJar --stacktrace --parallel --no-daemon - - name: Publish API - continue-on-error: true - run: | - ./gradlew :leaf-api:publish - ./gradlew publishDevBundlePublicationToLeafRepository -PpublishDevBundle=true - env: - REPO_USER: ${{ secrets.REPO_USER }} - REPO_PASSWORD: ${{ secrets.REPO_PASSWORD }} - - name: Rename Paperclip JARs run: | mv leaf-server/build/libs/leaf-paperclip-1.21.4-R0.1-SNAPSHOT-mojmap.jar ./leaf-1.21.4-${{ env.BUILD_NUMBER }}-mojmap.jar diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index cedfc8c4..fd66d8b9 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -14,12 +14,11 @@ jobs: - name: Checkout repository uses: actions/checkout@main - - name: Set up GraalVM JDK 21 - uses: graalvm/setup-graalvm@main + - name: Setup java + uses: useblacksmith/setup-java@v5 with: - java-version: 21 - github-token: "${{ secrets.GITHUB_TOKEN }}" - cache: gradle + distribution: 'temurin' + java-version: '21' - name: Grant execute permission for gradlew run: chmod +x gradlew diff --git a/.github/workflows/publish-api.yml b/.github/workflows/publish-api.yml new file mode 100644 index 00000000..71bf488a --- /dev/null +++ b/.github/workflows/publish-api.yml @@ -0,0 +1,54 @@ +name: Publish API +on: + push: + branches: [ "ver/1.21.4" ] + +jobs: + build: + runs-on: ubuntu-latest + env: + BUILD_NUMBER: ${{ github.run_number }} + GRADLE_MEMORY: "-Xmx4g -XX:MaxMetaspaceSize=2g" + steps: + - name: Checkout repository + uses: actions/checkout@main + with: + fetch-depth: 0 + + - name: Set up GraalVM JDK 21 + uses: graalvm/setup-graalvm@main + with: + java-version: 21 + github-token: "${{ secrets.GITHUB_TOKEN }}" + cache: gradle + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Configure Git + run: | + git config --global user.email "no-reply@github.com" + git config --global user.name "Github Actions" + + - name: Apply patches + run: | + PARALLELISM=$(($(nproc) * 2)) + ./gradlew -Dorg.gradle.jvmargs="${{ env.GRADLE_MEMORY }}" \ + -Dleaf.patcher.parallelism=$PARALLELISM \ + -Dleaf.patcher.incremental=true \ + applyAllPatches \ + --stacktrace --parallel \ + --max-workers=$PARALLELISM \ + --build-cache \ + --no-daemon + + - name: Build + run: ./gradlew build -x test + + - name: Publish API + continue-on-error: true + run: | + ./gradlew :leaf-api:publish + ./gradlew publishDevBundlePublicationToLeafRepository -PpublishDevBundle=true + env: + REPO_USER: ${{ secrets.REPO_USER }} + REPO_PASSWORD: ${{ secrets.REPO_PASSWORD }} From b17a0b35509d01ccbc99e1f5af5470e1c061eff4 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Fri, 25 Apr 2025 13:06:52 -0400 Subject: [PATCH 21/45] Move leaf config saving after config post load --- .../main/java/org/dreeam/leaf/config/ConfigModules.java | 7 +++++++ .../src/main/java/org/dreeam/leaf/config/LeafConfig.java | 5 ++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModules.java b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModules.java index 8df9a5c5..0a030cfa 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModules.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/ConfigModules.java @@ -46,6 +46,13 @@ public abstract class ConfigModules extends LeafConfig { for (ConfigModules module : MODULES) { module.onPostLoaded(); } + + // Save config to disk + try { + LeafConfig.config().saveConfig(); + } catch (Exception e) { + LeafConfig.LOGGER.error("Failed to save config file!", e); + } } private static List getAnnotatedStaticFields(Class clazz, Class annotation) { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java index 2d868c62..230e6b77 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java @@ -58,6 +58,7 @@ public class LeafConfig { /* Load & Reload */ + // Reload config (async) public static @NotNull CompletableFuture reloadAsync(CommandSender sender) { return CompletableFuture.runAsync(() -> { try { @@ -76,6 +77,7 @@ public class LeafConfig { }, Util.ioPool()); } + // Init config public static void loadConfig() { try { long begin = System.nanoTime(); @@ -100,9 +102,6 @@ public class LeafConfig { // Load config modules ConfigModules.initModules(); - - // Save config to disk - leafGlobalConfig.saveConfig(); } public static LeafGlobalConfig config() { From 97f00175198810e7dbce96e137b3ea800b9dcfe1 Mon Sep 17 00:00:00 2001 From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> Date: Sat, 26 Apr 2025 07:32:35 +1400 Subject: [PATCH 22/45] Use a dedicated thread pool for PWT event rescheduling --- ...58-optimize-BlockEntityType-isValid.patch} | 0 ...-SparklyPaper-Parallel-world-ticking.patch | 16 +++------ .../leaf/async/world/PWTEventScheduler.java | 35 +++++++++++++++++++ 3 files changed, 39 insertions(+), 12 deletions(-) rename leaf-server/minecraft-patches/features/{0159-optimize-BlockEntityType-isValid.patch => 0158-optimize-BlockEntityType-isValid.patch} (100%) create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/async/world/PWTEventScheduler.java diff --git a/leaf-server/minecraft-patches/features/0159-optimize-BlockEntityType-isValid.patch b/leaf-server/minecraft-patches/features/0158-optimize-BlockEntityType-isValid.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0159-optimize-BlockEntityType-isValid.patch rename to leaf-server/minecraft-patches/features/0158-optimize-BlockEntityType-isValid.patch diff --git a/leaf-server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch b/leaf-server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch index 6b552bea..2defa582 100644 --- a/leaf-server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch +++ b/leaf-server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch @@ -238,24 +238,16 @@ index a4aa2615823d77920ff55b8aa0bcc27a54b8c3e1..2fb65ce228da94eb7d9364ee0f945823 + // SparklyPaper end - parallel world ticking } diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -index 548fcd9646dee0c40b6ba9b3dafb9ca157dfe324..67f69ea3c28578cb73d2df662d246f0056ff2cb2 100644 +index 548fcd9646dee0c40b6ba9b3dafb9ca157dfe324..d7af94890bfccd6ff665d920cecfa1e5be626aa4 100644 --- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -@@ -28,6 +28,7 @@ import java.util.logging.Level; - class PaperEventManager { - - private final Server server; -+ private final org.purpurmc.purpur.util.MinecraftInternalPlugin minecraftInternalPlugin = new org.purpurmc.purpur.util.MinecraftInternalPlugin(); // Leaf - Parallel world ticking - - public PaperEventManager(Server server) { - this.server = server; -@@ -40,6 +41,12 @@ class PaperEventManager { +@@ -40,6 +40,12 @@ class PaperEventManager { if (listeners.length == 0) return; // Leaf end - Skip event if no listeners if (event.isAsynchronous() && this.server.isPrimaryThread()) { + // Leaf start - Parallel world ticking + if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.runAsyncTasksSync) { -+ org.bukkit.Bukkit.getAsyncScheduler().runNow(minecraftInternalPlugin, task -> event.callEvent()); ++ org.dreeam.leaf.async.world.PWTEventScheduler.getScheduler().scheduleTask(event::callEvent); + return; + } + // Leaf end - Parallel world ticking @@ -349,7 +341,7 @@ index af33cab59932f4ec135caf94dc5828930833daf6..92463ddc6fdcf542ce4a6d2a5059d4a9 } // Paper end diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index 973b297a22c0cc53f966582c67c3688f4b2205c7..61f9e88a7760b4bf23674ac8594dd31557768217 100644 +index 811823a1a7e24a19a7e37eb4c08efdfa19e839ed..b94efcab12d41fa8745e5bb55cfa6481d8262e74 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java @@ -75,6 +75,11 @@ public class CraftBlock implements Block { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/world/PWTEventScheduler.java b/leaf-server/src/main/java/org/dreeam/leaf/async/world/PWTEventScheduler.java new file mode 100644 index 00000000..eef0d654 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/world/PWTEventScheduler.java @@ -0,0 +1,35 @@ +package org.dreeam.leaf.async.world; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class PWTEventScheduler { + private static volatile PWTEventScheduler instance; + private final ExecutorService executor; + private PWTEventScheduler() { + this.executor = Executors.newCachedThreadPool( + new ThreadFactoryBuilder() + .setNameFormat("Leaf PWT Event Scheduler Thread - %d") + .setDaemon(true) + .setPriority(Thread.NORM_PRIORITY - 2) + .build() + ); + } + + public static PWTEventScheduler getScheduler() { + if (instance == null) { + synchronized (PWTEventScheduler.class) { + if (instance == null) { + instance = new PWTEventScheduler(); + } + } + } + return instance; + } + + public void scheduleTask(Runnable task) { + this.executor.execute(task); + } +} From 1d19912f1ae3b500902cb5ec4110baf85888a654 Mon Sep 17 00:00:00 2001 From: Taiyou06 Date: Fri, 25 Apr 2025 20:52:07 +0200 Subject: [PATCH 23/45] add back the lambda --- .../features/0157-Optimise-chunkUnloads.patch | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch b/leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch index d9564a21..61b8d35b 100644 --- a/leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch +++ b/leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch @@ -4,44 +4,6 @@ Date: Mon, 14 Apr 2025 20:07:52 +0200 Subject: [PATCH] Optimise chunkUnloads -diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java -index e4a5fa25ed368fc4662c30934da2963ef446d782..62ad5fe1196cd982b0d48b1e4903d036262ac54b 100644 ---- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java -+++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/NewChunkHolder.java -@@ -1753,23 +1753,20 @@ public final class NewChunkHolder { - chunk.tryMarkSaved(); - - final CallbackCompletable completable = new CallbackCompletable<>(); -- -- final Runnable run = () -> { -- final CompoundTag data = chunkData.write(); -- -- completable.complete(data); -- -- if (unloading) { -- NewChunkHolder.this.completeAsyncUnloadDataSave(MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, data); -- } -- }; -- - final PrioritisedExecutor.PrioritisedTask task; -+ - if (unloading) { -- this.chunkDataUnload.toRun().setRunnable(run); -+ this.chunkDataUnload.toRun().setRunnable(() -> { -+ final CompoundTag data = chunkData.write(); -+ completable.complete(data); -+ NewChunkHolder.this.completeAsyncUnloadDataSave(MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, data); -+ }); - task = this.chunkDataUnload.task(); - } else { -- task = this.scheduler.saveExecutor.createTask(run); -+ task = this.scheduler.saveExecutor.createTask(() -> { -+ final CompoundTag data = chunkData.write(); -+ completable.complete(data); -+ }); - } - - task.queue(); diff --git a/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java b/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java index 4ca68a903e67606fc4ef0bfa9862a73797121c8b..bed3a64388bb43e47c2ba4e67f7dde5b990d9578 100644 --- a/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java From 5abe1dfb9ad86b2dba598853e555399b9ce53dbb Mon Sep 17 00:00:00 2001 From: Taiyou06 Date: Sat, 26 Apr 2025 01:38:59 +0200 Subject: [PATCH 24/45] no need for this, already immutable --- .../0158-optimize-BlockEntityType-isValid.patch | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0158-optimize-BlockEntityType-isValid.patch b/leaf-server/minecraft-patches/features/0158-optimize-BlockEntityType-isValid.patch index af50473a..d1814b1c 100644 --- a/leaf-server/minecraft-patches/features/0158-optimize-BlockEntityType-isValid.patch +++ b/leaf-server/minecraft-patches/features/0158-optimize-BlockEntityType-isValid.patch @@ -5,17 +5,10 @@ Subject: [PATCH] optimize BlockEntityType#isValid diff --git a/net/minecraft/world/level/block/entity/BlockEntityType.java b/net/minecraft/world/level/block/entity/BlockEntityType.java -index a2a674f18d7f2c2e50a6b25f9ac3bf4534275976..bcfd5dc20d20fe6b74d726c7086a0cc7ae2565de 100644 +index a2a674f18d7f2c2e50a6b25f9ac3bf4534275976..073b2ca9f1af94934afc39104e0916c97b84ef0e 100644 --- a/net/minecraft/world/level/block/entity/BlockEntityType.java +++ b/net/minecraft/world/level/block/entity/BlockEntityType.java -@@ -246,12 +246,20 @@ public class BlockEntityType { - } - - Util.fetchChoiceType(References.BLOCK_ENTITY, name); -- return Registry.register(BuiltInRegistries.BLOCK_ENTITY_TYPE, name, new BlockEntityType<>(factory, Set.of(validBlocks))); -+ return Registry.register(BuiltInRegistries.BLOCK_ENTITY_TYPE, name, new BlockEntityType<>(factory, com.google.common.collect.ImmutableSet.copyOf(validBlocks))); // Leaf - ensure immutable - } - +@@ -252,6 +252,14 @@ public class BlockEntityType { private BlockEntityType(BlockEntityType.BlockEntitySupplier factory, Set validBlocks) { this.factory = factory; this.validBlocks = validBlocks; From 1974f348799d5b0ac4601fc1560dbf732569aebe Mon Sep 17 00:00:00 2001 From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> Date: Sun, 27 Apr 2025 00:22:03 +1400 Subject: [PATCH 25/45] General patch cleanup & New reject policy --- ...ntLong2ReferenceChainedHashTable-wit.patch | 79 ++++++------------- ...timize-ThreadedTicketLevelPropagator.patch | 40 +++++----- .../leaf/async/ai/AsyncGoalExecutor.java | 9 ++- .../async/path/PathfindTaskRejectPolicy.java | 3 +- .../modules/async/AsyncMobSpawning.java | 2 +- .../modules/async/AsyncPathfinding.java | 6 +- .../modules/async/AsyncTargetFinding.java | 5 +- .../modules/async/MultithreadedTracker.java | 3 +- .../SparklyPaperParallelWorldTicking.java | 2 +- .../leaf/config/modules/opt/FastRNG.java | 4 +- 10 files changed, 61 insertions(+), 92 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0153-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch b/leaf-server/minecraft-patches/features/0153-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch index 11c5606a..209b1f89 100644 --- a/leaf-server/minecraft-patches/features/0153-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch +++ b/leaf-server/minecraft-patches/features/0153-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch @@ -6,39 +6,30 @@ Subject: [PATCH] Replace ConcurrentLong2ReferenceChainedHashTable with custom diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java b/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java -index 7eafc5b7cba23d8dec92ecc1050afe3fd8c9e309..c2d5e83f0bdf98d3c07d6da2bba3b1ebaf7307d5 100644 +index 7eafc5b7cba23d8dec92ecc1050afe3fd8c9e309..7a299490d502147ddfd533637da36ea0d2ac918a 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java -@@ -7,6 +7,8 @@ import com.google.gson.JsonElement; - import com.google.gson.JsonObject; - import it.unimi.dsi.fastutil.longs.LongIterator; - import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; -+import org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable; -+ - import java.util.ArrayList; - import java.util.Iterator; - import java.util.List; -@@ -16,7 +18,7 @@ public final class ChunkUnloadQueue { +@@ -16,7 +16,7 @@ public final class ChunkUnloadQueue { public final int coordinateShift; private final AtomicLong orderGenerator = new AtomicLong(); - private final ConcurrentLong2ReferenceChainedHashTable unloadSections = new ConcurrentLong2ReferenceChainedHashTable<>(); -+ private final LeafConcurrentLong2ReferenceChainedHashTable unloadSections = new LeafConcurrentLong2ReferenceChainedHashTable<>(); ++ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable unloadSections = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>(); /* * Note: write operations do not occur in parallel for any given section. -@@ -32,8 +34,8 @@ public final class ChunkUnloadQueue { +@@ -32,8 +32,8 @@ public final class ChunkUnloadQueue { public List retrieveForAllRegions() { final List ret = new ArrayList<>(); - for (final Iterator> iterator = this.unloadSections.entryIterator(); iterator.hasNext();) { - final ConcurrentLong2ReferenceChainedHashTable.TableEntry entry = iterator.next(); -+ for (final Iterator> iterator = this.unloadSections.entryIterator(); iterator.hasNext();) { -+ final LeafConcurrentLong2ReferenceChainedHashTable.TableEntry entry = iterator.next(); ++ for (final Iterator> iterator = this.unloadSections.entryIterator(); iterator.hasNext(); ) { ++ final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable.TableEntry entry = iterator.next(); final long key = entry.getKey(); final UnloadSection section = entry.getValue(); final int sectionX = CoordinateUtils.getChunkX(key); -@@ -141,4 +143,4 @@ public final class ChunkUnloadQueue { +@@ -141,4 +141,4 @@ public final class ChunkUnloadQueue { this.order = order; } } @@ -46,90 +37,64 @@ index 7eafc5b7cba23d8dec92ecc1050afe3fd8c9e309..c2d5e83f0bdf98d3c07d6da2bba3b1eb \ No newline at end of file +} diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -index 06ac3537f5655d048d770bb004243f207fad9faa..a1f328a5c4ccc030c99762a68008ab1ecebdc06e 100644 +index 06ac3537f5655d048d770bb004243f207fad9faa..647354efeb18694ac56a542bf4a88be61136f6a8 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -@@ -40,6 +40,7 @@ import net.minecraft.util.SortedArraySet; - import net.minecraft.util.Unit; - import net.minecraft.world.level.ChunkPos; - import net.minecraft.world.level.chunk.LevelChunk; -+import org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable; - import org.slf4j.Logger; - import java.io.IOException; - import java.text.DecimalFormat; -@@ -71,11 +72,11 @@ public final class ChunkHolderManager { +@@ -71,11 +71,11 @@ public final class ChunkHolderManager { private static final long PROBE_MARKER = Long.MIN_VALUE + 1; public final ReentrantAreaLock ticketLockArea; - private final ConcurrentLong2ReferenceChainedHashTable>> tickets = new ConcurrentLong2ReferenceChainedHashTable<>(); - private final ConcurrentLong2ReferenceChainedHashTable sectionToChunkToExpireCount = new ConcurrentLong2ReferenceChainedHashTable<>(); -+ private final LeafConcurrentLong2ReferenceChainedHashTable>> tickets = new LeafConcurrentLong2ReferenceChainedHashTable<>(); -+ private final LeafConcurrentLong2ReferenceChainedHashTable sectionToChunkToExpireCount = new LeafConcurrentLong2ReferenceChainedHashTable<>(); ++ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable>> tickets = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>(); ++ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable sectionToChunkToExpireCount = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>(); final ChunkUnloadQueue unloadQueue; - private final ConcurrentLong2ReferenceChainedHashTable chunkHolders = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(16384, 0.25f); -+ private final LeafConcurrentLong2ReferenceChainedHashTable chunkHolders = LeafConcurrentLong2ReferenceChainedHashTable.createWithCapacity(16384, 0.25f); ++ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable chunkHolders = org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable.createWithCapacity(16384, 0.25f); private final ServerLevel world; private final ChunkTaskScheduler taskScheduler; private long currentTick; -@@ -1422,9 +1423,9 @@ public final class ChunkHolderManager { +@@ -1422,9 +1422,9 @@ public final class ChunkHolderManager { final JsonArray allTicketsJson = new JsonArray(); ret.add("tickets", allTicketsJson); - for (final Iterator>>> iterator = this.tickets.entryIterator(); -+ for (final Iterator>>> iterator = this.tickets.entryIterator(); ++ for (final Iterator>>> iterator = this.tickets.entryIterator(); iterator.hasNext();) { - final ConcurrentLong2ReferenceChainedHashTable.TableEntry>> coordinateTickets = iterator.next(); -+ final LeafConcurrentLong2ReferenceChainedHashTable.TableEntry>> coordinateTickets = iterator.next(); ++ final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable.TableEntry>> coordinateTickets = iterator.next(); final long coordinate = coordinateTickets.getKey(); final SortedArraySet> tickets = coordinateTickets.getValue(); diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java -index 310a8f80debadd64c2d962ebf83b7d0505ce6e42..b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4 100644 +index 310a8f80debadd64c2d962ebf83b7d0505ce6e42..01598347ea545f2ff2ac337086345d7369a64520 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java -@@ -10,6 +10,8 @@ import it.unimi.dsi.fastutil.longs.Long2ByteLinkedOpenHashMap; - import it.unimi.dsi.fastutil.shorts.Short2ByteLinkedOpenHashMap; - import it.unimi.dsi.fastutil.shorts.Short2ByteMap; - import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; -+import org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable; -+ - import java.lang.invoke.VarHandle; - import java.util.ArrayDeque; - import java.util.ArrayList; -@@ -35,11 +37,11 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -35,11 +35,11 @@ public abstract class ThreadedTicketLevelPropagator { } private final UpdateQueue updateQueue; - private final ConcurrentLong2ReferenceChainedHashTable
sections; -+ private final LeafConcurrentLong2ReferenceChainedHashTable
sections; ++ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable
sections; public ThreadedTicketLevelPropagator() { this.updateQueue = new UpdateQueue(); - this.sections = new ConcurrentLong2ReferenceChainedHashTable<>(); -+ this.sections = new LeafConcurrentLong2ReferenceChainedHashTable<>(); ++ this.sections = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>(); } // must hold ticket lock for: diff --git a/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java b/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java -index 1487b7d8be435b3fbad2aabd05796965b4775a87..54c425ba84c7c70becdfbde08812afdde777f5a8 100644 +index 1487b7d8be435b3fbad2aabd05796965b4775a87..a1459e3bc2076575e3f52d9e2b7d49630afeb799 100644 --- a/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java +++ b/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java -@@ -27,6 +27,8 @@ import net.minecraft.world.level.chunk.LightChunkGetter; - import net.minecraft.world.level.chunk.status.ChunkStatus; - import net.minecraft.world.level.lighting.LayerLightEventListener; - import net.minecraft.world.level.lighting.LevelLightEngine; -+import org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable; -+ - import java.util.ArrayDeque; - import java.util.ArrayList; - import java.util.HashSet; -@@ -740,7 +742,7 @@ public final class StarLightInterface { +@@ -740,7 +740,7 @@ public final class StarLightInterface { public static final class ServerLightQueue extends LightQueue { - private final ConcurrentLong2ReferenceChainedHashTable chunkTasks = new ConcurrentLong2ReferenceChainedHashTable<>(); -+ private final LeafConcurrentLong2ReferenceChainedHashTable chunkTasks = new LeafConcurrentLong2ReferenceChainedHashTable<>(); ++ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable chunkTasks = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>(); public ServerLightQueue(final StarLightInterface lightInterface) { super(lightInterface); diff --git a/leaf-server/minecraft-patches/features/0155-Optimize-ThreadedTicketLevelPropagator.patch b/leaf-server/minecraft-patches/features/0155-Optimize-ThreadedTicketLevelPropagator.patch index cc977ac4..f34bc01c 100644 --- a/leaf-server/minecraft-patches/features/0155-Optimize-ThreadedTicketLevelPropagator.patch +++ b/leaf-server/minecraft-patches/features/0155-Optimize-ThreadedTicketLevelPropagator.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Optimize ThreadedTicketLevelPropagator diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java -index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc1de204b7 100644 +index 01598347ea545f2ff2ac337086345d7369a64520..f7b995909cb580d03de3bf9ee20a6e36ef8aaaab 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java -@@ -780,11 +780,13 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -778,11 +778,13 @@ public abstract class ThreadedTicketLevelPropagator { // minimum number of bits to represent [0, SECTION_SIZE * SECTION_CACHE_WIDTH) private static final int COORDINATE_BITS = 9; private static final int COORDINATE_SIZE = 1 << COORDINATE_BITS; @@ -22,7 +22,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc // index = x + (z * SECTION_CACHE_WIDTH) // (this requires x >= 0 and z >= 0) private final Section[] sections = new Section[SECTION_CACHE_WIDTH * SECTION_CACHE_WIDTH]; -@@ -828,8 +830,8 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -826,8 +828,8 @@ public abstract class ThreadedTicketLevelPropagator { // must hold ticket lock for (centerSectionX,centerSectionZ) in radius rad // must call setupEncodeOffset private final void setupCaches(final ThreadedTicketLevelPropagator propagator, @@ -33,7 +33,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc for (int dz = -rad; dz <= rad; ++dz) { for (int dx = -rad; dx <= rad; ++dx) { final int sectionX = centerSectionX + dx; -@@ -847,29 +849,29 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -845,29 +847,29 @@ public abstract class ThreadedTicketLevelPropagator { } private final void setSectionInCache(final int sectionX, final int sectionZ, final Section section) { @@ -70,7 +70,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc } } -@@ -882,8 +884,8 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -880,8 +882,8 @@ public abstract class ThreadedTicketLevelPropagator { // next LEVEL_BITS (6) bits: propagated level [0, 63] // propagation directions bitset (16 bits): private static final long ALL_DIRECTIONS_BITSET = ( @@ -81,7 +81,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc (1L << ((1 + 0) | ((1 - 1) << 2))) | (1L << ((1 + 1) | ((1 - 1) << 2))) | -@@ -920,7 +922,7 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -918,7 +920,7 @@ public abstract class ThreadedTicketLevelPropagator { } private void ch(long bs, int shift) { @@ -90,7 +90,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc for (int i = 0, len = Integer.bitCount(bitset); i < len; ++i) { final int set = Integer.numberOfTrailingZeros(bitset); final int tailingBit = (-bitset) & bitset; -@@ -1000,27 +1002,38 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -998,27 +1000,38 @@ public abstract class ThreadedTicketLevelPropagator { final int decodeOffsetZ = -this.encodeOffsetZ; final int encodeOffset = this.coordinateOffset; final int sectionOffset = this.sectionIndexOffset; @@ -135,7 +135,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc } // this bitset represents the values that we have not propagated to -@@ -1036,8 +1049,8 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1034,8 +1047,8 @@ public abstract class ThreadedTicketLevelPropagator { // must guarantee that either we propagate everything in 1 radius or we partially propagate for 1 radius // but the rest not propagated are already handled long currentPropagation = ~( @@ -146,7 +146,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc (1L << ((2 + 0) | ((2 - 1) << 3))) | (1L << ((2 + 1) | ((2 - 1) << 3))) | -@@ -1095,7 +1108,7 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1093,7 +1106,7 @@ public abstract class ThreadedTicketLevelPropagator { currentPropagation ^= (bitsetLine1 | bitsetLine2 | bitsetLine3); // now try to propagate @@ -155,7 +155,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc // lower 8 bits are current level, next upper 7 bits are source level, next 1 bit is updated source flag final short currentStoredLevel = section.levels[localIndex]; -@@ -1106,8 +1119,8 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1104,8 +1117,8 @@ public abstract class ThreadedTicketLevelPropagator { } // update level @@ -166,7 +166,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc // queue next if (toPropagate > 1) { -@@ -1115,7 +1128,7 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1113,7 +1126,7 @@ public abstract class ThreadedTicketLevelPropagator { // the child bitset is 4x4, so we just shift each line by 4 // add the propagation bitset offset to each line to make it easy to OR it into the propagation queue value final long childPropagation = @@ -175,7 +175,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc ((bitsetLine2 >>> (start + 8)) << (4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = 0 ((bitsetLine3 >>> (start + (8 + 8))) << (4 + 4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); // z = 1 -@@ -1125,7 +1138,7 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1123,7 +1136,7 @@ public abstract class ThreadedTicketLevelPropagator { queue = this.resizeIncreaseQueue(); } queue[queueLength++] = @@ -184,7 +184,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc ((toPropagate & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | childPropagation; //(ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); continue; -@@ -1146,18 +1159,19 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1144,18 +1157,19 @@ public abstract class ThreadedTicketLevelPropagator { final int decodeOffsetZ = -this.encodeOffsetZ; final int encodeOffset = this.coordinateOffset; final int sectionOffset = this.sectionIndexOffset; @@ -208,7 +208,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc // this bitset represents the values that we have not propagated to // this bitset lets us determine what directions the neighbours we set should propagate to, in most cases -@@ -1172,8 +1186,8 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1170,8 +1184,8 @@ public abstract class ThreadedTicketLevelPropagator { // must guarantee that either we propagate everything in 1 radius or we partially propagate for 1 radius // but the rest not propagated are already handled long currentPropagation = ~( @@ -219,7 +219,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc (1L << ((2 + 0) | ((2 - 1) << 3))) | (1L << ((2 + 1) | ((2 - 1) << 3))) | -@@ -1229,7 +1243,7 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1227,7 +1241,7 @@ public abstract class ThreadedTicketLevelPropagator { final long bitsetLine3 = currentPropagation & (7L << (start + (8 + 8))); // now try to propagate @@ -228,7 +228,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc // lower 8 bits are current level, next upper 7 bits are source level, next 1 bit is updated source flag final short currentStoredLevel = section.levels[localIndex]; -@@ -1246,7 +1260,7 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1244,7 +1258,7 @@ public abstract class ThreadedTicketLevelPropagator { increaseQueue = this.resizeIncreaseQueue(); } increaseQueue[increaseQueueLength++] = @@ -237,7 +237,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc ((currentLevel & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | (FLAG_RECHECK_LEVEL | (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS))); continue; -@@ -1257,8 +1271,8 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1255,8 +1269,8 @@ public abstract class ThreadedTicketLevelPropagator { //currentPropagation ^= (bitsetLine1 | bitsetLine2 | bitsetLine3); // update level @@ -248,7 +248,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc if (sourceLevel != 0) { // re-propagate source -@@ -1267,7 +1281,7 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1265,7 +1279,7 @@ public abstract class ThreadedTicketLevelPropagator { increaseQueue = this.resizeIncreaseQueue(); } increaseQueue[increaseQueueLength++] = @@ -257,7 +257,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc ((sourceLevel & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | (FLAG_WRITE_LEVEL | (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS))); } -@@ -1278,7 +1292,7 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1276,7 +1290,7 @@ public abstract class ThreadedTicketLevelPropagator { // the child bitset is 4x4, so we just shift each line by 4 // add the propagation bitset offset to each line to make it easy to OR it into the propagation queue value final long childPropagation = @@ -266,7 +266,7 @@ index b69d256e2f6bab3c1b90c5f8c42caa3d80cd67a4..e8dddc7fca4b0383844be5337a87c4bc ((bitsetLine2 >>> (start + 8)) << (4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = 0 ((bitsetLine3 >>> (start + (8 + 8))) << (4 + 4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); // z = 1 -@@ -1288,7 +1302,7 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1286,7 +1300,7 @@ public abstract class ThreadedTicketLevelPropagator { queue = this.resizeDecreaseQueue(); } queue[queueLength++] = diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java index 45e9aba3..d27be899 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java @@ -1,14 +1,15 @@ package org.dreeam.leaf.async.ai; -import org.jetbrains.annotations.Nullable; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.util.List; +import java.util.concurrent.ExecutorService; public class AsyncGoalExecutor { - @Nullable - public static java.util.concurrent.ExecutorService EXECUTOR; + public static ExecutorService EXECUTOR; - public static final org.apache.logging.log4j.Logger LOGGER = org.apache.logging.log4j.LogManager.getLogger("Leaf Async Entity Lookup"); + public static final Logger LOGGER = LogManager.getLogger("Leaf Async Entity Lookup"); public static void runTasks(List tasks) { for (Runnable task : tasks) { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/path/PathfindTaskRejectPolicy.java b/leaf-server/src/main/java/org/dreeam/leaf/async/path/PathfindTaskRejectPolicy.java index 4e195f44..c4026a0b 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/path/PathfindTaskRejectPolicy.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/path/PathfindTaskRejectPolicy.java @@ -6,7 +6,8 @@ import java.util.Locale; public enum PathfindTaskRejectPolicy { FLUSH_ALL, - CALLER_RUNS; + CALLER_RUNS, + DISCARD; public static PathfindTaskRejectPolicy fromString(String policy) { try { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java index 21e21882..60902f7d 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java @@ -22,7 +22,7 @@ public class AsyncMobSpawning extends ConfigModules { This just offloads some expensive calculations that are required for mob spawning.""", """ 是否异步化生物生成. - 在实体较多的服务器上, 异步生成可最高带来15%的性能提升. + 在实体较多的服务器上, 异步生成可最高带来 15% 的性能提升. 须在Paper配置文件中打开 per-player-mob-spawns 才能生效."""); // This prevents us from changing the value during a reload. diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java index 6109e12d..5b19711e 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java @@ -24,11 +24,13 @@ public class AsyncPathfinding extends ConfigModules { """ The policy to use when the queue is full and a new task is submitted. FLUSH_ALL: All pending tasks will be run on server thread. - CALLER_RUNS: Newly submitted task will be run on server thread.""", + CALLER_RUNS: Newly submitted task will be run on server thread. + DISCARD: Newly submitted task will be dropped directly.""", """ 当队列满时, 新提交的任务将使用以下策略处理. FLUSH_ALL: 所有等待中的任务都将在主线程上运行. - CALLER_RUNS: 新提交的任务将在主线程上运行.""" + CALLER_RUNS: 新提交的任务将在主线程上运行. + DISCARD: 新提交的任务会被直接丢弃.""" ); if (asyncPathfindingInitialized) { config.getConfigSection(getBasePath()); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java index 822f142f..dc6442c4 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java @@ -1,10 +1,10 @@ package org.dreeam.leaf.config.modules.async; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.dreeam.leaf.async.ai.AsyncGoalExecutor; import org.dreeam.leaf.config.ConfigModules; import org.dreeam.leaf.config.EnumConfigCategory; -import org.dreeam.leaf.config.LeafConfig; import org.dreeam.leaf.config.annotations.Experimental; import java.util.concurrent.ArrayBlockingQueue; @@ -62,12 +62,11 @@ public class AsyncTargetFinding extends ConfigModules { 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(128), - new com.google.common.util.concurrent.ThreadFactoryBuilder() + new ThreadFactoryBuilder() .setNameFormat("Leaf Async Target Finding Thread") .setDaemon(true) .setPriority(Thread.NORM_PRIORITY - 2) .build(), new ThreadPoolExecutor.CallerRunsPolicy()); - LeafConfig.LOGGER.info("Using 1 threads for Async Target Finding"); } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java index 31e80606..1d71923f 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java @@ -18,7 +18,8 @@ public class MultithreadedTracker extends ConfigModules { @Override public void onLoaded() { - config.addCommentRegionBased(getBasePath(), """ + config.addCommentRegionBased(getBasePath(), + """ Make entity tracking saving asynchronously, can improve performance significantly, especially in some massive entities in small area situations.""", """ diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java index f668a123..811cb169 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java @@ -23,7 +23,7 @@ public class SparklyPaperParallelWorldTicking extends ConfigModules { config.addCommentRegionBased(getBasePath(), """ **Experimental feature** - Enables parallel world ticking to improve performance on multi-core systems..""", + Enables parallel world ticking to improve performance on multi-core systems.""", """ **实验性功能** 启用并行世界处理以提高多核 CPU 使用率."""); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/FastRNG.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/FastRNG.java index 0c344d7a..c51dd431 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/FastRNG.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/FastRNG.java @@ -67,8 +67,8 @@ public class FastRNG extends ConfigModules { Use direct random implementation instead of delegating to Java's RandomGenerator. This may improve performance but potentially changes RNG behavior.""", """ - 使用直接随机实现而不是委托给Java的RandomGenerator. - 这可能会提高性能,但可能会改变RNG行为。""")); + 使用直接随机实现而不是委派给 RandomGenerator. + 这可能会提高性能, 但可能会改变 RNG 行为.""")); if (enabled) { try { From 1bddbe19f017ceb02f707b4b5d33e18da4a86194 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Mon, 28 Apr 2025 23:35:26 +0800 Subject: [PATCH 26/45] optimize AttributeMap (#299) * optimize AttributeMap * update multithreaded tracker config * use non-sync collection when MT disabled * cleanup --- .../features/0085-Multithreaded-Tracker.patch | 371 ++++++++++++++++-- .../modules/async/MultithreadedTracker.java | 17 +- 2 files changed, 354 insertions(+), 34 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch b/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch index 5d23ec55..2d726019 100644 --- a/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch +++ b/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch @@ -177,7 +177,7 @@ index f106373ef3ac4a8685c2939c9e8361688a285913..51ae390c68e7a3aa193329cc3bc47ca6 public boolean visible = true; diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java -index d8298c7925e3bcea07ead4d438478cc51abcfa16..75670751064add901c2628d53d8028350f966c5d 100644 +index d8298c7925e3bcea07ead4d438478cc51abcfa16..78798a84c6b49708f2650b52b40e397e4f26b532 100644 --- a/net/minecraft/server/level/ServerEntity.java +++ b/net/minecraft/server/level/ServerEntity.java @@ -110,8 +110,16 @@ public class ServerEntity { @@ -199,24 +199,15 @@ index d8298c7925e3bcea07ead4d438478cc51abcfa16..75670751064add901c2628d53d802835 } } ); -@@ -435,12 +443,15 @@ public class ServerEntity { - if (this.entity instanceof LivingEntity) { - Set attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync(); - if (!attributesToSync.isEmpty()) { -+ // Leaf start - petal - Multithreaded tracker - send in main thread -+ final Set copy = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(attributesToSync); - // CraftBukkit start - Send scaled max health - if (this.entity instanceof ServerPlayer serverPlayer) { -- serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false); -+ serverPlayer.getBukkitEntity().injectScaledMaxHealth(copy, false); - } - // CraftBukkit end -- this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); -+ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), copy)); -+ // Leaf end - petal - Multithreaded tracker - send in main thread +@@ -443,7 +451,7 @@ public class ServerEntity { + this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); } - attributesToSync.clear(); +- attributesToSync.clear(); ++ ((LivingEntity)this.entity).getAttributes().getAttributesIdToSync().clear(); // Leaf - Multithreaded tracker + } + } + diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java index d6ebc25dc5f04194edde5ad3a1166113e5542a1d..49cbdf014d0626b36eb4c451b6de09508822b7fd 100644 --- a/net/minecraft/server/level/ServerLevel.java @@ -252,8 +243,33 @@ index 04bf8bba0d8c0d5459605253dcc3f135bf43fd95..abe79d07196de0a10a382d4c37161c7e // Paper start - Prevent teleporting dead entities if (this.player.isRemoved()) { LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java +index d502325d693539842fd6f5485365e0e9b786b7aa..6a0b1d4f17e9c3c3152ea1696ecb4a054abd56ce 100644 +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -1317,7 +1317,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + this.onAttributeUpdated(attributeInstance.getAttribute()); + } + +- attributesToUpdate.clear(); ++ this.getAttributes().getAttributesIdToUpdate().clear(); // Leaf - Multithreaded tracker + } + + protected void onAttributeUpdated(Holder attribute) { +diff --git a/net/minecraft/world/entity/ai/attributes/Attribute.java b/net/minecraft/world/entity/ai/attributes/Attribute.java +index f8419dde44ebc7324e783f8bee42132d5ec973c3..8b18ec1644eb743483ed6ed79e9c98d287fd7e20 100644 +--- a/net/minecraft/world/entity/ai/attributes/Attribute.java ++++ b/net/minecraft/world/entity/ai/attributes/Attribute.java +@@ -16,6 +16,7 @@ public class Attribute { + private boolean syncable; + private final String descriptionId; + private Attribute.Sentiment sentiment = Attribute.Sentiment.POSITIVE; ++ public int uniqueId; // Leaf + + protected Attribute(String descriptionId, double defaultValue) { + this.defaultValue = defaultValue; diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -index 8013594bb4844e7a8abf28123958e7f632d39341..ceff383d565267edd13a6d9006030b8e1f8053e3 100644 +index 8013594bb4844e7a8abf28123958e7f632d39341..80c703fe85ec21e4d218823504f4bbf7826b2fa6 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java @@ -24,8 +24,11 @@ public class AttributeInstance { @@ -264,34 +280,329 @@ index 8013594bb4844e7a8abf28123958e7f632d39341..ceff383d565267edd13a6d9006030b8e - private final Map permanentModifiers = new Object2ObjectArrayMap<>(); + // Leaf start - Multithreaded tracker + private final boolean multiThreadedTrackingEnabled = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled; -+ private final Map modifierById = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new Object2ObjectArrayMap<>(); -+ private final Map permanentModifiers = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new Object2ObjectArrayMap<>(); ++ private final Map modifierById = multiThreadedTrackingEnabled ? it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this) : new Object2ObjectArrayMap<>(); ++ private final Map permanentModifiers = multiThreadedTrackingEnabled ? it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this) : new Object2ObjectArrayMap<>(); + // Leaf end - Multithreaded tracker private double baseValue; private boolean dirty = true; private double cachedValue; +@@ -114,7 +117,15 @@ public class AttributeInstance { + } + + protected void setDirty() { +- this.dirty = true; ++ // Leaf start - Multithreaded tracker ++ if (multiThreadedTrackingEnabled) { ++ synchronized (this) { ++ this.dirty = true; ++ } ++ } else { ++ this.dirty = true; ++ } ++ // Leaf end - Multithreaded tracker + this.onDirty.accept(this); + } + +@@ -141,6 +152,17 @@ public class AttributeInstance { + } + + public double getValue() { ++ // Leaf start - Multithreaded tracker ++ if (multiThreadedTrackingEnabled) { ++ synchronized (this) { ++ if (this.dirty) { ++ this.cachedValue = this.calculateValue(); ++ this.dirty = false; ++ } ++ return this.cachedValue; ++ } ++ } ++ // Leaf end - Multithreaded tracker + if (this.dirty) { + this.cachedValue = this.calculateValue(); + this.dirty = false; diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java -index 89f4c5b2d61e27acd48063f9f24ce9ea91898b8b..371dd51c62c9a109014851c8a1562a5cb78b18b6 100644 +index 89f4c5b2d61e27acd48063f9f24ce9ea91898b8b..9e26059285e56b9337478f5f21aa48741b828a76 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java -@@ -19,11 +19,14 @@ import org.slf4j.Logger; +@@ -19,13 +19,13 @@ import org.slf4j.Logger; public class AttributeMap { private static final Logger LOGGER = LogUtils.getLogger(); -+ // Leaf start - Multithreaded tracker -+ private final boolean multiThreadedTrackingEnabled = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled; - // Gale start - Lithium - replace AI attributes with optimized collections +- // Gale start - Lithium - replace AI attributes with optimized collections - private final Map, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); - private final Set attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); - private final Set attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); -+ private final Map, AttributeInstance> attributes = multiThreadedTrackingEnabled ? new java.util.concurrent.ConcurrentHashMap<>() : new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); -+ private final Set attributesToSync = multiThreadedTrackingEnabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); -+ private final Set attributesToUpdate = multiThreadedTrackingEnabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); - // Gale end - Lithium - replace AI attributes with optimized collections +- // Gale end - Lithium - replace AI attributes with optimized collections ++ // Leaf start - Multithreaded tracker ++ private final java.util.concurrent.atomic.AtomicReferenceArray attributes; ++ private final it.unimi.dsi.fastutil.ints.IntSet attributesToSync; ++ private final it.unimi.dsi.fastutil.ints.IntSet attributesToUpdate; ++ private final java.util.function.Consumer onDirty; + // Leaf end - Multithreaded tracker private final AttributeSupplier supplier; - private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations +- private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables + + public AttributeMap(AttributeSupplier supplier) { +@@ -36,54 +36,115 @@ public class AttributeMap { + this.entity = entity; + // Purpur end - Ridables + this.supplier = defaultAttributes; +- this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations ++ // Leaf start - Multithreaded tracker ++ this.attributes = new java.util.concurrent.atomic.AtomicReferenceArray<>(BuiltInRegistries.ATTRIBUTE.size()); ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { ++ this.attributesToSync = it.unimi.dsi.fastutil.ints.IntSets.synchronize(new it.unimi.dsi.fastutil.ints.IntArraySet()); ++ this.attributesToUpdate = it.unimi.dsi.fastutil.ints.IntSets.synchronize(new it.unimi.dsi.fastutil.ints.IntArraySet()); ++ } else { ++ this.attributesToSync = new it.unimi.dsi.fastutil.ints.IntArraySet(); ++ this.attributesToUpdate = new it.unimi.dsi.fastutil.ints.IntArraySet(); ++ } ++ this.onDirty = this::onAttributeModified; ++ // Leaf end - Multithreaded tracker + } + + private void onAttributeModified(AttributeInstance instance) { +- this.attributesToUpdate.add(instance); ++ this.attributesToUpdate.add(instance.getAttribute().value().uniqueId); + if (instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))) { // Purpur - Ridables +- this.attributesToSync.add(instance); ++ this.attributesToSync.add(instance.getAttribute().value().uniqueId); + } + } + +- public Set getAttributesToSync() { ++ // Leaf start - Multithreaded tracker ++ public it.unimi.dsi.fastutil.ints.IntSet getAttributesIdToSync() { + return this.attributesToSync; + } + +- public Set getAttributesToUpdate() { ++ public it.unimi.dsi.fastutil.ints.IntSet getAttributesIdToUpdate() { + return this.attributesToUpdate; + } + ++ @Deprecated ++ public Set getAttributesToSync() { ++ var set = new it.unimi.dsi.fastutil.objects.ObjectArraySet(); ++ for (int i : this.attributesToSync.toIntArray()) { ++ var attribute = this.attributes.get(i); ++ if (attribute == null) { ++ throw new NullPointerException("attribute cannot be null"); ++ } ++ set.add(attribute); ++ } ++ return set; ++ } ++ ++ @Deprecated ++ public Set getAttributesToUpdate() { ++ var set = new it.unimi.dsi.fastutil.objects.ObjectArraySet(); ++ for (int i : this.attributesToUpdate.toIntArray()) { ++ var attribute = this.attributes.get(i); ++ if (attribute == null) { ++ throw new NullPointerException("attribute cannot be null"); ++ } ++ set.add(attribute); ++ } ++ return set; ++ } ++ // Leaf end - Multithreaded tracker ++ + public Collection getSyncableAttributes() { +- return this.attributes.values().stream().filter(instance -> instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))).collect(Collectors.toList()); // Purpur - Ridables ++ // Leaf start ++ var attrs = this.attributes; ++ var result = new java.util.ArrayList(); ++ int len = attrs.length(); ++ for (int i = 0; i < len; i++) { ++ AttributeInstance instance = attrs.get(i); ++ if (instance != null ++ && instance.getAttribute().value().isClientSyncable() ++ && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value())) ++ ) { ++ result.add(instance); ++ } ++ } ++ return result; ++ // Leaf end + } + + @Nullable + public AttributeInstance getInstance(Holder attribute) { +- return this.attributes.computeIfAbsent(attribute, this.createInstance); // Gale - Airplane - reduce entity allocations - cache lambda, as for some reason java allocates it anyways ++ // Leaf start ++ int id = attribute.value().uniqueId; ++ var attr = this.attributes.getAcquire(id); ++ if (attr != null) { ++ return attr; ++ } ++ var newAttr = this.supplier.createInstance(this.onDirty, attribute); ++ attributes.compareAndExchangeRelease(id, null, newAttr); ++ return attributes.getAcquire(id); ++ // Leaf end + } + + public boolean hasAttribute(Holder attribute) { +- return this.attributes.get(attribute) != null || this.supplier.hasAttribute(attribute); ++ return this.attributes.get(attribute.value().uniqueId) != null || this.supplier.hasAttribute(attribute); // Leaf + } + + public boolean hasModifier(Holder attribute, ResourceLocation id) { +- AttributeInstance attributeInstance = this.attributes.get(attribute); ++ AttributeInstance attributeInstance = this.attributes.get(attribute.value().uniqueId); // Leaf + return attributeInstance != null ? attributeInstance.getModifier(id) != null : this.supplier.hasModifier(attribute, id); + } + + public double getValue(Holder attribute) { +- AttributeInstance attributeInstance = this.attributes.get(attribute); ++ AttributeInstance attributeInstance = this.attributes.get(attribute.value().uniqueId); // Leaf + return attributeInstance != null ? attributeInstance.getValue() : this.supplier.getValue(attribute); + } + + public double getBaseValue(Holder attribute) { +- AttributeInstance attributeInstance = this.attributes.get(attribute); ++ AttributeInstance attributeInstance = this.attributes.get(attribute.value().uniqueId); // Leaf + return attributeInstance != null ? attributeInstance.getBaseValue() : this.supplier.getBaseValue(attribute); + } + + public double getModifierValue(Holder attribute, ResourceLocation id) { +- AttributeInstance attributeInstance = this.attributes.get(attribute); ++ AttributeInstance attributeInstance = this.attributes.get(attribute.value().uniqueId); // Leaf + return attributeInstance != null ? attributeInstance.getModifier(id).amount() : this.supplier.getModifierValue(attribute, id); + } + +@@ -99,7 +160,7 @@ public class AttributeMap { + + public void removeAttributeModifiers(Multimap, AttributeModifier> modifiers) { + modifiers.asMap().forEach((holder, collection) -> { +- AttributeInstance attributeInstance = this.attributes.get(holder); ++ AttributeInstance attributeInstance = this.attributes.get(holder.value().uniqueId); // Leaf + if (attributeInstance != null) { + collection.forEach(attributeModifier -> attributeInstance.removeModifier(attributeModifier.id())); + } +@@ -107,37 +168,58 @@ public class AttributeMap { + } + + public void assignAllValues(AttributeMap map) { +- map.attributes.values().forEach(attribute -> { +- AttributeInstance instance = this.getInstance(attribute.getAttribute()); ++ // Leaf start ++ var attrs = map.attributes; ++ int len = attrs.length(); ++ for (int i = 0; i < len; i++) { ++ AttributeInstance instance = attrs.get(i); + if (instance != null) { +- instance.replaceFrom(attribute); ++ AttributeInstance self = this.getInstance(instance.getAttribute()); ++ if (self != null) { ++ self.replaceFrom(instance); ++ } + } +- }); ++ } ++ // Leaf end + } + + public void assignBaseValues(AttributeMap map) { +- map.attributes.values().forEach(attribute -> { +- AttributeInstance instance = this.getInstance(attribute.getAttribute()); ++ // Leaf start ++ var attrs = map.attributes; ++ int len = attrs.length(); ++ for (int i = 0; i < len; i++) { ++ AttributeInstance instance = attrs.get(i); + if (instance != null) { +- instance.setBaseValue(attribute.getBaseValue()); ++ AttributeInstance self = this.getInstance(instance.getAttribute()); ++ if (self != null) { ++ self.setBaseValue(instance.getBaseValue()); ++ } + } +- }); ++ } ++ // Leaf end + } + + public void assignPermanentModifiers(AttributeMap map) { +- map.attributes.values().forEach(attribute -> { +- AttributeInstance instance = this.getInstance(attribute.getAttribute()); ++ // Leaf start ++ var attrs = map.attributes; ++ int len = attrs.length(); ++ for (int i = 0; i < len; i++) { ++ AttributeInstance instance = attrs.get(i); + if (instance != null) { +- instance.addPermanentModifiers(attribute.getPermanentModifiers()); ++ AttributeInstance self = this.getInstance(instance.getAttribute()); ++ if (self != null) { ++ self.addPermanentModifiers(instance.getPermanentModifiers()); ++ } + } +- }); ++ } ++ // Leaf end + } + + public boolean resetBaseValue(Holder attribute) { + if (!this.supplier.hasAttribute(attribute)) { + return false; + } else { +- AttributeInstance attributeInstance = this.attributes.get(attribute); ++ AttributeInstance attributeInstance = this.attributes.get(attribute.value().uniqueId); // Leaf + if (attributeInstance != null) { + attributeInstance.setBaseValue(this.supplier.getBaseValue(attribute)); + } +@@ -149,9 +231,16 @@ public class AttributeMap { + public ListTag save() { + ListTag listTag = new ListTag(); + +- for (AttributeInstance attributeInstance : this.attributes.values()) { +- listTag.add(attributeInstance.save()); ++ // Leaf start ++ var attrs = this.attributes; ++ int len = attrs.length(); ++ for (int i = 0; i < len; i++) { ++ AttributeInstance instance = attrs.get(i); ++ if (instance != null) { ++ listTag.add(instance.save()); ++ } + } ++ // Leaf end + + return listTag; + } +@@ -177,7 +266,7 @@ public class AttributeMap { + // Paper - start - living entity allow attribute registration + public void registerAttribute(Holder attributeBase) { + AttributeInstance attributeModifiable = new AttributeInstance(attributeBase, AttributeInstance::getAttribute); +- attributes.put(attributeBase, attributeModifiable); ++ attributes.setRelease(attributeBase.value().uniqueId, attributeModifiable); // Leaf + } + // Paper - end - living entity allow attribute registration + +diff --git a/net/minecraft/world/entity/ai/attributes/Attributes.java b/net/minecraft/world/entity/ai/attributes/Attributes.java +index 26e4f766640f28068b6c07174285768e7d251b4f..59dca559ff875d710ecc5bafda23b7125b86b8ac 100644 +--- a/net/minecraft/world/entity/ai/attributes/Attributes.java ++++ b/net/minecraft/world/entity/ai/attributes/Attributes.java +@@ -6,6 +6,8 @@ import net.minecraft.core.registries.BuiltInRegistries; + import net.minecraft.resources.ResourceLocation; + + public class Attributes { ++ private static final java.util.concurrent.atomic.AtomicInteger LENGTH = new java.util.concurrent.atomic.AtomicInteger(0); // Leaf ++ + public static final Holder ARMOR = register("armor", new RangedAttribute("attribute.name.armor", 0.0, 0.0, 30.0).setSyncable(true)); + public static final Holder ARMOR_TOUGHNESS = register( + "armor_toughness", new RangedAttribute("attribute.name.armor_toughness", 0.0, 0.0, 20.0).setSyncable(true) +@@ -93,6 +95,7 @@ public class Attributes { + ); + + private static Holder register(String name, Attribute attribute) { ++ attribute.uniqueId = LENGTH.getAndIncrement(); // Leaf + return Registry.registerForHolder(BuiltInRegistries.ATTRIBUTE, ResourceLocation.withDefaultNamespace(name), attribute); + } + diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java index 681dec447486138088fe5f705ef4fadab531139f..27f8a22d798a17dbd5949d1b6ff0526837fe91d5 100644 --- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java index 1d71923f..e18028b4 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java @@ -15,6 +15,7 @@ public class MultithreadedTracker extends ConfigModules { public static int asyncEntityTrackerMaxThreads = 0; public static int asyncEntityTrackerKeepalive = 60; public static int asyncEntityTrackerQueueSize = 0; + private static boolean asyncMultithreadedTrackerInitialized; @Override public void onLoaded() { @@ -25,15 +26,23 @@ public class MultithreadedTracker extends ConfigModules { """ 异步实体跟踪, 在实体数量多且密集的情况下效果明显."""); - - enabled = config.getBoolean(getBasePath() + ".enabled", enabled); - compatModeEnabled = config.getBoolean(getBasePath() + ".compat-mode", compatModeEnabled, config.pickStringRegionBased(""" + config.addCommentRegionBased(getBasePath() + ".compat-mode", + """ Enable compat mode ONLY if Citizens or NPC plugins using real entity has installed, Compat mode fixed visible issue with player type NPCs of Citizens, But still recommend to use packet based / virtual entity NPC plugin, e.g. ZNPC Plus, Adyeshach, Fancy NPC or else.""", """ 是否启用兼容模式, - 如果你的服务器安装了 Citizens 或其他类似非发包 NPC 插件, 请开启此项.""")); + 如果你的服务器安装了 Citizens 或其他类似非发包 NPC 插件, 请开启此项."""); + + if (asyncMultithreadedTrackerInitialized) { + config.getConfigSection(getBasePath()); + return; + } + asyncMultithreadedTrackerInitialized = true; + + enabled = config.getBoolean(getBasePath() + ".enabled", enabled); + compatModeEnabled = config.getBoolean(getBasePath() + ".compat-mode", compatModeEnabled); asyncEntityTrackerMaxThreads = config.getInt(getBasePath() + ".max-threads", asyncEntityTrackerMaxThreads); asyncEntityTrackerKeepalive = config.getInt(getBasePath() + ".keepalive", asyncEntityTrackerKeepalive); asyncEntityTrackerQueueSize = config.getInt(getBasePath() + ".queue-size", asyncEntityTrackerQueueSize); From 92876d4b12012ce5e2a4c37299ef10c83b0b4c3c Mon Sep 17 00:00:00 2001 From: Taiyou06 Date: Mon, 28 Apr 2025 20:47:15 +0200 Subject: [PATCH 27/45] fix NPE on isParallelCancelledByPlugin --- ...134-SparklyPaper-Parallel-world-ticking.patch | 16 ++++++++++++---- ...e-chunk-retrieving-in-entity-fluid-push.patch | 6 +++--- .../features/0154-Async-target-finding.patch | 8 ++++---- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch b/leaf-server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch index 25ad7ee0..505f0d9c 100644 --- a/leaf-server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch +++ b/leaf-server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch @@ -743,10 +743,10 @@ index b17c8a2f5294ac28cc05fb05c84a041b2c6c8721..0b8b4658dbbad1bacc13e97b4fc0cdce serverPlayer.connection = player.connection; serverPlayer.restoreFrom(player, keepInventory); diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 9bc978ca290ca772b0367e89b69fe16b502b0cd2..17ff57db6f90b3f4facef691ad4347e67a9f5836 100644 +index 9bc978ca290ca772b0367e89b69fe16b502b0cd2..2f98d035b32d3a9b3366dbea0ac52b24d06dd373 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java -@@ -3370,15 +3370,33 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -3370,15 +3370,41 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess if (this.portalProcess != null) { if (this.portalProcess.processPortalTeleportation(serverLevel, this, this.canUsePortal(false))) { this.setPortalCooldown(); @@ -760,6 +760,12 @@ index 9bc978ca290ca772b0367e89b69fe16b502b0cd2..17ff57db6f90b3f4facef691ad4347e6 + // TCRF SparklyPaper (Pathothingi) start - parallel world ticking + java.util.function.Consumer portalEntityTask = entity -> { + assert entity.portalProcess != null; ++ // Leaf start - Fix NPE when portalProcess becomes null before task execution ++ if (entity.portalProcess == null) { ++ // Portal process was likely nulled out (e.g., expired) between scheduling and execution. ++ return; ++ } ++ // Leaf end - Fix NPE + + if (entity.portalProcess.isParallelCancelledByPlugin()) { + entity.portalProcess = null; @@ -775,8 +781,10 @@ index 9bc978ca290ca772b0367e89b69fe16b502b0cd2..17ff57db6f90b3f4facef691ad4347e6 + entity.teleport(portalDestination); + } + } -+ if (this.portalProcess != null) ++ // Add another null check here just in case teleport() somehow nulled it (defensive) ++ if (entity.portalProcess != null) { + entity.portalProcess.confirmParallelAsHandled(); ++ } + }; + if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { + this.portalProcess.setParallelAsScheduled(); @@ -787,7 +795,7 @@ index 9bc978ca290ca772b0367e89b69fe16b502b0cd2..17ff57db6f90b3f4facef691ad4347e6 } else if (this.portalProcess.hasExpired()) { this.portalProcess = null; } -@@ -3908,6 +3926,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -3908,6 +3934,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } private Entity teleportCrossDimension(ServerLevel level, TeleportTransition teleportTransition) { diff --git a/leaf-server/minecraft-patches/features/0150-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch b/leaf-server/minecraft-patches/features/0150-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch index 544682a3..069ad7ae 100644 --- a/leaf-server/minecraft-patches/features/0150-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch +++ b/leaf-server/minecraft-patches/features/0150-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch @@ -11,10 +11,10 @@ As part of: Airplane (https://github.com/TECHNOVE/Airplane) Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 17ff57db6f90b3f4facef691ad4347e67a9f5836..075fcbcde23b5bb7b27ff622e8d188c3a2583973 100644 +index 2f98d035b32d3a9b3366dbea0ac52b24d06dd373..90879616842cc61d15854b07f56f6fcb89f11074 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java -@@ -4614,10 +4614,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4622,10 +4622,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess // Paper start - optimise collisions public boolean updateFluidHeightAndDoFluidPushing(final TagKey fluid, final double flowScale) { @@ -26,7 +26,7 @@ index 17ff57db6f90b3f4facef691ad4347e67a9f5836..075fcbcde23b5bb7b27ff622e8d188c3 final AABB boundingBox = this.getBoundingBox().deflate(1.0E-3); final Level world = this.level; -@@ -4653,7 +4650,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4661,7 +4658,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { diff --git a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch index 7c36b574..82309c9a 100644 --- a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch @@ -77,7 +77,7 @@ index 90bdcd168ad5b1a940f81b191bd59a34d3a33070..051bf2ec9c312460b1379cea0f8a2a3a // Paper - rewrite chunk system diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 075fcbcde23b5bb7b27ff622e8d188c3a2583973..e5aa7c542b1e9c9362aa1feeeebef45919feac70 100644 +index 90879616842cc61d15854b07f56f6fcb89f11074..0ae36986492d98f46ccf409f6b71ed9cfca488e7 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -243,6 +243,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @@ -88,7 +88,7 @@ index 075fcbcde23b5bb7b27ff622e8d188c3a2583973..e5aa7c542b1e9c9362aa1feeeebef459 public static final float DEFAULT_BB_WIDTH = 0.6F; public static final float DEFAULT_BB_HEIGHT = 1.8F; public float moveDist; -@@ -5028,7 +5029,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -5036,7 +5037,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public final boolean isRemoved() { @@ -103,7 +103,7 @@ index 075fcbcde23b5bb7b27ff622e8d188c3a2583973..e5aa7c542b1e9c9362aa1feeeebef459 } @Nullable -@@ -5055,6 +5062,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -5063,6 +5070,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers if (this.removalReason == null) { this.removalReason = removalReason; @@ -115,7 +115,7 @@ index 075fcbcde23b5bb7b27ff622e8d188c3a2583973..e5aa7c542b1e9c9362aa1feeeebef459 } if (this.removalReason.shouldDestroy()) { -@@ -5074,6 +5086,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -5082,6 +5094,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess public void unsetRemoved() { this.removalReason = null; From c892477ec332d54695205d295fb50db8142b8fe0 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Tue, 29 Apr 2025 16:40:42 +0800 Subject: [PATCH 28/45] revert optimize AttributeMap MythicMobs access the map --- .../features/0085-Multithreaded-Tracker.patch | 317 ++---------------- 1 file changed, 31 insertions(+), 286 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch b/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch index 2d726019..f322c327 100644 --- a/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch +++ b/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch @@ -177,7 +177,7 @@ index f106373ef3ac4a8685c2939c9e8361688a285913..51ae390c68e7a3aa193329cc3bc47ca6 public boolean visible = true; diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java -index d8298c7925e3bcea07ead4d438478cc51abcfa16..78798a84c6b49708f2650b52b40e397e4f26b532 100644 +index d8298c7925e3bcea07ead4d438478cc51abcfa16..865e98a7afe116fe42c5ad0d8a96adbd92bc8dc0 100644 --- a/net/minecraft/server/level/ServerEntity.java +++ b/net/minecraft/server/level/ServerEntity.java @@ -110,8 +110,16 @@ public class ServerEntity { @@ -199,12 +199,25 @@ index d8298c7925e3bcea07ead4d438478cc51abcfa16..78798a84c6b49708f2650b52b40e397e } } ); -@@ -443,7 +451,7 @@ public class ServerEntity { - this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); +@@ -435,15 +443,18 @@ public class ServerEntity { + if (this.entity instanceof LivingEntity) { + Set attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync(); + if (!attributesToSync.isEmpty()) { ++ // Leaf start - petal - Multithreaded tracker - send in main thread ++ final Set copy = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(attributesToSync); + // CraftBukkit start - Send scaled max health + if (this.entity instanceof ServerPlayer serverPlayer) { +- serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false); ++ serverPlayer.getBukkitEntity().injectScaledMaxHealth(copy, false); + } + // CraftBukkit end +- this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); ++ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), copy)); ++ // Leaf end - petal - Multithreaded tracker - send in main thread } - attributesToSync.clear(); -+ ((LivingEntity)this.entity).getAttributes().getAttributesIdToSync().clear(); // Leaf - Multithreaded tracker ++ ((LivingEntity)this.entity).getAttributes().getAttributesToSync().clear(); // Leaf - Multithreaded tracker } } @@ -243,31 +256,6 @@ index 04bf8bba0d8c0d5459605253dcc3f135bf43fd95..abe79d07196de0a10a382d4c37161c7e // Paper start - Prevent teleporting dead entities if (this.player.isRemoved()) { LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); -diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java -index d502325d693539842fd6f5485365e0e9b786b7aa..6a0b1d4f17e9c3c3152ea1696ecb4a054abd56ce 100644 ---- a/net/minecraft/world/entity/LivingEntity.java -+++ b/net/minecraft/world/entity/LivingEntity.java -@@ -1317,7 +1317,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.onAttributeUpdated(attributeInstance.getAttribute()); - } - -- attributesToUpdate.clear(); -+ this.getAttributes().getAttributesIdToUpdate().clear(); // Leaf - Multithreaded tracker - } - - protected void onAttributeUpdated(Holder attribute) { -diff --git a/net/minecraft/world/entity/ai/attributes/Attribute.java b/net/minecraft/world/entity/ai/attributes/Attribute.java -index f8419dde44ebc7324e783f8bee42132d5ec973c3..8b18ec1644eb743483ed6ed79e9c98d287fd7e20 100644 ---- a/net/minecraft/world/entity/ai/attributes/Attribute.java -+++ b/net/minecraft/world/entity/ai/attributes/Attribute.java -@@ -16,6 +16,7 @@ public class Attribute { - private boolean syncable; - private final String descriptionId; - private Attribute.Sentiment sentiment = Attribute.Sentiment.POSITIVE; -+ public int uniqueId; // Leaf - - protected Attribute(String descriptionId, double defaultValue) { - this.defaultValue = defaultValue; diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java index 8013594bb4844e7a8abf28123958e7f632d39341..80c703fe85ec21e4d218823504f4bbf7826b2fa6 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java @@ -322,10 +310,10 @@ index 8013594bb4844e7a8abf28123958e7f632d39341..80c703fe85ec21e4d218823504f4bbf7 this.cachedValue = this.calculateValue(); this.dirty = false; diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java -index 89f4c5b2d61e27acd48063f9f24ce9ea91898b8b..9e26059285e56b9337478f5f21aa48741b828a76 100644 +index 89f4c5b2d61e27acd48063f9f24ce9ea91898b8b..d6e7685cb0e9beaa017bcc665f0f1c7c29c2ca5f 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java -@@ -19,13 +19,13 @@ import org.slf4j.Logger; +@@ -19,11 +19,11 @@ import org.slf4j.Logger; public class AttributeMap { private static final Logger LOGGER = LogUtils.getLogger(); @@ -335,274 +323,31 @@ index 89f4c5b2d61e27acd48063f9f24ce9ea91898b8b..9e26059285e56b9337478f5f21aa4874 - private final Set attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); - // Gale end - Lithium - replace AI attributes with optimized collections + // Leaf start - Multithreaded tracker -+ private final java.util.concurrent.atomic.AtomicReferenceArray attributes; -+ private final it.unimi.dsi.fastutil.ints.IntSet attributesToSync; -+ private final it.unimi.dsi.fastutil.ints.IntSet attributesToUpdate; -+ private final java.util.function.Consumer onDirty; ++ private final Map, AttributeInstance> attributes; ++ private final Set attributesToSync; ++ private final Set attributesToUpdate; + // Leaf end - Multithreaded tracker private final AttributeSupplier supplier; -- private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations + private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables - - public AttributeMap(AttributeSupplier supplier) { -@@ -36,54 +36,115 @@ public class AttributeMap { - this.entity = entity; +@@ -37,6 +37,17 @@ public class AttributeMap { // Purpur end - Ridables this.supplier = defaultAttributes; -- this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations + this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations + // Leaf start - Multithreaded tracker -+ this.attributes = new java.util.concurrent.atomic.AtomicReferenceArray<>(BuiltInRegistries.ATTRIBUTE.size()); + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { -+ this.attributesToSync = it.unimi.dsi.fastutil.ints.IntSets.synchronize(new it.unimi.dsi.fastutil.ints.IntArraySet()); -+ this.attributesToUpdate = it.unimi.dsi.fastutil.ints.IntSets.synchronize(new it.unimi.dsi.fastutil.ints.IntArraySet()); ++ this.attributes = it.unimi.dsi.fastutil.objects.Reference2ReferenceMaps.synchronize(new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0)); ++ this.attributesToSync = it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new it.unimi.dsi.fastutil.objects.ReferenceArraySet<>(0)); ++ this.attributesToUpdate = it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new it.unimi.dsi.fastutil.objects.ReferenceArraySet<>(0)); + } else { -+ this.attributesToSync = new it.unimi.dsi.fastutil.ints.IntArraySet(); -+ this.attributesToUpdate = new it.unimi.dsi.fastutil.ints.IntArraySet(); ++ this.attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); ++ this.attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceArraySet<>(0); ++ this.attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceArraySet<>(0); + } -+ this.onDirty = this::onAttributeModified; + // Leaf end - Multithreaded tracker } private void onAttributeModified(AttributeInstance instance) { -- this.attributesToUpdate.add(instance); -+ this.attributesToUpdate.add(instance.getAttribute().value().uniqueId); - if (instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))) { // Purpur - Ridables -- this.attributesToSync.add(instance); -+ this.attributesToSync.add(instance.getAttribute().value().uniqueId); - } - } - -- public Set getAttributesToSync() { -+ // Leaf start - Multithreaded tracker -+ public it.unimi.dsi.fastutil.ints.IntSet getAttributesIdToSync() { - return this.attributesToSync; - } - -- public Set getAttributesToUpdate() { -+ public it.unimi.dsi.fastutil.ints.IntSet getAttributesIdToUpdate() { - return this.attributesToUpdate; - } - -+ @Deprecated -+ public Set getAttributesToSync() { -+ var set = new it.unimi.dsi.fastutil.objects.ObjectArraySet(); -+ for (int i : this.attributesToSync.toIntArray()) { -+ var attribute = this.attributes.get(i); -+ if (attribute == null) { -+ throw new NullPointerException("attribute cannot be null"); -+ } -+ set.add(attribute); -+ } -+ return set; -+ } -+ -+ @Deprecated -+ public Set getAttributesToUpdate() { -+ var set = new it.unimi.dsi.fastutil.objects.ObjectArraySet(); -+ for (int i : this.attributesToUpdate.toIntArray()) { -+ var attribute = this.attributes.get(i); -+ if (attribute == null) { -+ throw new NullPointerException("attribute cannot be null"); -+ } -+ set.add(attribute); -+ } -+ return set; -+ } -+ // Leaf end - Multithreaded tracker -+ - public Collection getSyncableAttributes() { -- return this.attributes.values().stream().filter(instance -> instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))).collect(Collectors.toList()); // Purpur - Ridables -+ // Leaf start -+ var attrs = this.attributes; -+ var result = new java.util.ArrayList(); -+ int len = attrs.length(); -+ for (int i = 0; i < len; i++) { -+ AttributeInstance instance = attrs.get(i); -+ if (instance != null -+ && instance.getAttribute().value().isClientSyncable() -+ && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value())) -+ ) { -+ result.add(instance); -+ } -+ } -+ return result; -+ // Leaf end - } - - @Nullable - public AttributeInstance getInstance(Holder attribute) { -- return this.attributes.computeIfAbsent(attribute, this.createInstance); // Gale - Airplane - reduce entity allocations - cache lambda, as for some reason java allocates it anyways -+ // Leaf start -+ int id = attribute.value().uniqueId; -+ var attr = this.attributes.getAcquire(id); -+ if (attr != null) { -+ return attr; -+ } -+ var newAttr = this.supplier.createInstance(this.onDirty, attribute); -+ attributes.compareAndExchangeRelease(id, null, newAttr); -+ return attributes.getAcquire(id); -+ // Leaf end - } - - public boolean hasAttribute(Holder attribute) { -- return this.attributes.get(attribute) != null || this.supplier.hasAttribute(attribute); -+ return this.attributes.get(attribute.value().uniqueId) != null || this.supplier.hasAttribute(attribute); // Leaf - } - - public boolean hasModifier(Holder attribute, ResourceLocation id) { -- AttributeInstance attributeInstance = this.attributes.get(attribute); -+ AttributeInstance attributeInstance = this.attributes.get(attribute.value().uniqueId); // Leaf - return attributeInstance != null ? attributeInstance.getModifier(id) != null : this.supplier.hasModifier(attribute, id); - } - - public double getValue(Holder attribute) { -- AttributeInstance attributeInstance = this.attributes.get(attribute); -+ AttributeInstance attributeInstance = this.attributes.get(attribute.value().uniqueId); // Leaf - return attributeInstance != null ? attributeInstance.getValue() : this.supplier.getValue(attribute); - } - - public double getBaseValue(Holder attribute) { -- AttributeInstance attributeInstance = this.attributes.get(attribute); -+ AttributeInstance attributeInstance = this.attributes.get(attribute.value().uniqueId); // Leaf - return attributeInstance != null ? attributeInstance.getBaseValue() : this.supplier.getBaseValue(attribute); - } - - public double getModifierValue(Holder attribute, ResourceLocation id) { -- AttributeInstance attributeInstance = this.attributes.get(attribute); -+ AttributeInstance attributeInstance = this.attributes.get(attribute.value().uniqueId); // Leaf - return attributeInstance != null ? attributeInstance.getModifier(id).amount() : this.supplier.getModifierValue(attribute, id); - } - -@@ -99,7 +160,7 @@ public class AttributeMap { - - public void removeAttributeModifiers(Multimap, AttributeModifier> modifiers) { - modifiers.asMap().forEach((holder, collection) -> { -- AttributeInstance attributeInstance = this.attributes.get(holder); -+ AttributeInstance attributeInstance = this.attributes.get(holder.value().uniqueId); // Leaf - if (attributeInstance != null) { - collection.forEach(attributeModifier -> attributeInstance.removeModifier(attributeModifier.id())); - } -@@ -107,37 +168,58 @@ public class AttributeMap { - } - - public void assignAllValues(AttributeMap map) { -- map.attributes.values().forEach(attribute -> { -- AttributeInstance instance = this.getInstance(attribute.getAttribute()); -+ // Leaf start -+ var attrs = map.attributes; -+ int len = attrs.length(); -+ for (int i = 0; i < len; i++) { -+ AttributeInstance instance = attrs.get(i); - if (instance != null) { -- instance.replaceFrom(attribute); -+ AttributeInstance self = this.getInstance(instance.getAttribute()); -+ if (self != null) { -+ self.replaceFrom(instance); -+ } - } -- }); -+ } -+ // Leaf end - } - - public void assignBaseValues(AttributeMap map) { -- map.attributes.values().forEach(attribute -> { -- AttributeInstance instance = this.getInstance(attribute.getAttribute()); -+ // Leaf start -+ var attrs = map.attributes; -+ int len = attrs.length(); -+ for (int i = 0; i < len; i++) { -+ AttributeInstance instance = attrs.get(i); - if (instance != null) { -- instance.setBaseValue(attribute.getBaseValue()); -+ AttributeInstance self = this.getInstance(instance.getAttribute()); -+ if (self != null) { -+ self.setBaseValue(instance.getBaseValue()); -+ } - } -- }); -+ } -+ // Leaf end - } - - public void assignPermanentModifiers(AttributeMap map) { -- map.attributes.values().forEach(attribute -> { -- AttributeInstance instance = this.getInstance(attribute.getAttribute()); -+ // Leaf start -+ var attrs = map.attributes; -+ int len = attrs.length(); -+ for (int i = 0; i < len; i++) { -+ AttributeInstance instance = attrs.get(i); - if (instance != null) { -- instance.addPermanentModifiers(attribute.getPermanentModifiers()); -+ AttributeInstance self = this.getInstance(instance.getAttribute()); -+ if (self != null) { -+ self.addPermanentModifiers(instance.getPermanentModifiers()); -+ } - } -- }); -+ } -+ // Leaf end - } - - public boolean resetBaseValue(Holder attribute) { - if (!this.supplier.hasAttribute(attribute)) { - return false; - } else { -- AttributeInstance attributeInstance = this.attributes.get(attribute); -+ AttributeInstance attributeInstance = this.attributes.get(attribute.value().uniqueId); // Leaf - if (attributeInstance != null) { - attributeInstance.setBaseValue(this.supplier.getBaseValue(attribute)); - } -@@ -149,9 +231,16 @@ public class AttributeMap { - public ListTag save() { - ListTag listTag = new ListTag(); - -- for (AttributeInstance attributeInstance : this.attributes.values()) { -- listTag.add(attributeInstance.save()); -+ // Leaf start -+ var attrs = this.attributes; -+ int len = attrs.length(); -+ for (int i = 0; i < len; i++) { -+ AttributeInstance instance = attrs.get(i); -+ if (instance != null) { -+ listTag.add(instance.save()); -+ } - } -+ // Leaf end - - return listTag; - } -@@ -177,7 +266,7 @@ public class AttributeMap { - // Paper - start - living entity allow attribute registration - public void registerAttribute(Holder attributeBase) { - AttributeInstance attributeModifiable = new AttributeInstance(attributeBase, AttributeInstance::getAttribute); -- attributes.put(attributeBase, attributeModifiable); -+ attributes.setRelease(attributeBase.value().uniqueId, attributeModifiable); // Leaf - } - // Paper - end - living entity allow attribute registration - -diff --git a/net/minecraft/world/entity/ai/attributes/Attributes.java b/net/minecraft/world/entity/ai/attributes/Attributes.java -index 26e4f766640f28068b6c07174285768e7d251b4f..59dca559ff875d710ecc5bafda23b7125b86b8ac 100644 ---- a/net/minecraft/world/entity/ai/attributes/Attributes.java -+++ b/net/minecraft/world/entity/ai/attributes/Attributes.java -@@ -6,6 +6,8 @@ import net.minecraft.core.registries.BuiltInRegistries; - import net.minecraft.resources.ResourceLocation; - - public class Attributes { -+ private static final java.util.concurrent.atomic.AtomicInteger LENGTH = new java.util.concurrent.atomic.AtomicInteger(0); // Leaf -+ - public static final Holder ARMOR = register("armor", new RangedAttribute("attribute.name.armor", 0.0, 0.0, 30.0).setSyncable(true)); - public static final Holder ARMOR_TOUGHNESS = register( - "armor_toughness", new RangedAttribute("attribute.name.armor_toughness", 0.0, 0.0, 20.0).setSyncable(true) -@@ -93,6 +95,7 @@ public class Attributes { - ); - - private static Holder register(String name, Attribute attribute) { -+ attribute.uniqueId = LENGTH.getAndIncrement(); // Leaf - return Registry.registerForHolder(BuiltInRegistries.ATTRIBUTE, ResourceLocation.withDefaultNamespace(name), attribute); - } - diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java index 681dec447486138088fe5f705ef4fadab531139f..27f8a22d798a17dbd5949d1b6ff0526837fe91d5 100644 --- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java From 259cdaa3d8ee1c094aad7d3b9886ff9aec61eb2a Mon Sep 17 00:00:00 2001 From: Taiyou06 Date: Tue, 29 Apr 2025 23:30:09 +0200 Subject: [PATCH 29/45] [ci skip] cleanup --- ...timize-ThreadedTicketLevelPropagator.patch | 221 +----------------- 1 file changed, 7 insertions(+), 214 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0155-Optimize-ThreadedTicketLevelPropagator.patch b/leaf-server/minecraft-patches/features/0155-Optimize-ThreadedTicketLevelPropagator.patch index f34bc01c..ce2f704e 100644 --- a/leaf-server/minecraft-patches/features/0155-Optimize-ThreadedTicketLevelPropagator.patch +++ b/leaf-server/minecraft-patches/features/0155-Optimize-ThreadedTicketLevelPropagator.patch @@ -5,92 +5,10 @@ Subject: [PATCH] Optimize ThreadedTicketLevelPropagator diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java -index 01598347ea545f2ff2ac337086345d7369a64520..f7b995909cb580d03de3bf9ee20a6e36ef8aaaab 100644 +index 01598347ea545f2ff2ac337086345d7369a64520..fbea9d83b7458760d4b1b5e1c8bd77ff46072b97 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java -@@ -778,11 +778,13 @@ public abstract class ThreadedTicketLevelPropagator { - // minimum number of bits to represent [0, SECTION_SIZE * SECTION_CACHE_WIDTH) - private static final int COORDINATE_BITS = 9; - private static final int COORDINATE_SIZE = 1 << COORDINATE_BITS; -+ - static { - if ((SECTION_SIZE * SECTION_CACHE_WIDTH) > (1 << COORDINATE_BITS)) { - throw new IllegalStateException("Adjust COORDINATE_BITS"); - } - } -+ - // index = x + (z * SECTION_CACHE_WIDTH) - // (this requires x >= 0 and z >= 0) - private final Section[] sections = new Section[SECTION_CACHE_WIDTH * SECTION_CACHE_WIDTH]; -@@ -826,8 +828,8 @@ public abstract class ThreadedTicketLevelPropagator { - // must hold ticket lock for (centerSectionX,centerSectionZ) in radius rad - // must call setupEncodeOffset - private final void setupCaches(final ThreadedTicketLevelPropagator propagator, -- final int centerSectionX, final int centerSectionZ, -- final int rad) { -+ final int centerSectionX, final int centerSectionZ, -+ final int rad) { - for (int dz = -rad; dz <= rad; ++dz) { - for (int dx = -rad; dx <= rad; ++dx) { - final int sectionX = centerSectionX + dx; -@@ -845,29 +847,29 @@ public abstract class ThreadedTicketLevelPropagator { - } - - private final void setSectionInCache(final int sectionX, final int sectionZ, final Section section) { -- this.sections[sectionX + SECTION_CACHE_WIDTH*sectionZ + this.sectionIndexOffset] = section; -+ this.sections[sectionX + SECTION_CACHE_WIDTH * sectionZ + this.sectionIndexOffset] = section; - } - - private final Section getSection(final int sectionX, final int sectionZ) { -- return this.sections[sectionX + SECTION_CACHE_WIDTH*sectionZ + this.sectionIndexOffset]; -+ return this.sections[sectionX + SECTION_CACHE_WIDTH * sectionZ + this.sectionIndexOffset]; - } - - private final int getLevel(final int posX, final int posZ) { -- final Section section = this.sections[(posX >> SECTION_SHIFT) + SECTION_CACHE_WIDTH*(posZ >> SECTION_SHIFT) + this.sectionIndexOffset]; -+ final Section section = this.sections[(posX >> SECTION_SHIFT) + SECTION_CACHE_WIDTH * (posZ >> SECTION_SHIFT) + this.sectionIndexOffset]; - if (section != null) { -- return (int)section.levels[(posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT)] & 0xFF; -+ return (int) section.levels[(posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT)] & 0xFF; - } - - return 0; - } - - private final void setLevel(final int posX, final int posZ, final int to) { -- final Section section = this.sections[(posX >> SECTION_SHIFT) + SECTION_CACHE_WIDTH*(posZ >> SECTION_SHIFT) + this.sectionIndexOffset]; -+ final Section section = this.sections[(posX >> SECTION_SHIFT) + SECTION_CACHE_WIDTH * (posZ >> SECTION_SHIFT) + this.sectionIndexOffset]; - if (section != null) { - final int index = (posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); - final short level = section.levels[index]; -- section.levels[index] = (short)((level & ~0xFF) | (to & 0xFF)); -- this.updatedPositions.put(CoordinateUtils.getChunkKey(posX, posZ), (byte)to); -+ section.levels[index] = (short) ((level & ~0xFF) | (to & 0xFF)); -+ this.updatedPositions.put(CoordinateUtils.getChunkKey(posX, posZ), (byte) to); - } - } - -@@ -880,8 +882,8 @@ public abstract class ThreadedTicketLevelPropagator { - // next LEVEL_BITS (6) bits: propagated level [0, 63] - // propagation directions bitset (16 bits): - private static final long ALL_DIRECTIONS_BITSET = ( -- // z = -1 -- (1L << ((1 - 1) | ((1 - 1) << 2))) | -+ // z = -1 -+ (1L << ((1 - 1) | ((1 - 1) << 2))) | - (1L << ((1 + 0) | ((1 - 1) << 2))) | - (1L << ((1 + 1) | ((1 - 1) << 2))) | - -@@ -918,7 +920,7 @@ public abstract class ThreadedTicketLevelPropagator { - } - - private void ch(long bs, int shift) { -- int bitset = (int)(bs >>> shift); -+ int bitset = (int) (bs >>> shift); - for (int i = 0, len = Integer.bitCount(bitset); i < len; ++i) { - final int set = Integer.numberOfTrailingZeros(bitset); - final int tailingBit = (-bitset) & bitset; -@@ -998,27 +1000,38 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -998,6 +998,7 @@ public abstract class ThreadedTicketLevelPropagator { final int decodeOffsetZ = -this.encodeOffsetZ; final int encodeOffset = this.coordinateOffset; final int sectionOffset = this.sectionIndexOffset; @@ -98,19 +16,8 @@ index 01598347ea545f2ff2ac337086345d7369a64520..f7b995909cb580d03de3bf9ee20a6e36 final Long2ByteLinkedOpenHashMap updatedPositions = this.updatedPositions; - while (queueReadIndex < queueLength) { - final long queueValue = queue[queueReadIndex++]; - -- final int posX = ((int)queueValue & (COORDINATE_SIZE - 1)) + decodeOffsetX; -- final int posZ = (((int)queueValue >>> COORDINATE_BITS) & (COORDINATE_SIZE - 1)) + decodeOffsetZ; -- final int propagatedLevel = ((int)queueValue >>> (COORDINATE_BITS + COORDINATE_BITS)) & (LEVEL_COUNT - 1); -+ final int posX = ((int) queueValue & (COORDINATE_SIZE - 1)) + decodeOffsetX; -+ final int posZ = (((int) queueValue >>> COORDINATE_BITS) & (COORDINATE_SIZE - 1)) + decodeOffsetZ; -+ final int propagatedLevel = ((int) queueValue >>> (COORDINATE_BITS + COORDINATE_BITS)) & (LEVEL_COUNT - 1); - // note: the above code requires coordinate bits * 2 < 32 - // bitset is 16 bits -- int propagateDirectionBitset = (int)(queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); -+ int propagateDirectionBitset = (int) (queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); +@@ -1012,13 +1013,23 @@ public abstract class ThreadedTicketLevelPropagator { + int propagateDirectionBitset = (int)(queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); if ((queueValue & FLAG_RECHECK_LEVEL) != 0L) { - if (this.getLevel(posX, posZ) != propagatedLevel) { @@ -135,18 +42,7 @@ index 01598347ea545f2ff2ac337086345d7369a64520..f7b995909cb580d03de3bf9ee20a6e36 } // this bitset represents the values that we have not propagated to -@@ -1034,8 +1047,8 @@ public abstract class ThreadedTicketLevelPropagator { - // must guarantee that either we propagate everything in 1 radius or we partially propagate for 1 radius - // but the rest not propagated are already handled - long currentPropagation = ~( -- // z = -1 -- (1L << ((2 - 1) | ((2 - 1) << 3))) | -+ // z = -1 -+ (1L << ((2 - 1) | ((2 - 1) << 3))) | - (1L << ((2 + 0) | ((2 - 1) << 3))) | - (1L << ((2 + 1) | ((2 - 1) << 3))) | - -@@ -1093,7 +1106,7 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1093,7 +1104,7 @@ public abstract class ThreadedTicketLevelPropagator { currentPropagation ^= (bitsetLine1 | bitsetLine2 | bitsetLine3); // now try to propagate @@ -155,36 +51,7 @@ index 01598347ea545f2ff2ac337086345d7369a64520..f7b995909cb580d03de3bf9ee20a6e36 // lower 8 bits are current level, next upper 7 bits are source level, next 1 bit is updated source flag final short currentStoredLevel = section.levels[localIndex]; -@@ -1104,8 +1117,8 @@ public abstract class ThreadedTicketLevelPropagator { - } - - // update level -- section.levels[localIndex] = (short)((currentStoredLevel & ~0xFF) | (toPropagate & 0xFF)); -- updatedPositions.putAndMoveToLast(CoordinateUtils.getChunkKey(offX, offZ), (byte)toPropagate); -+ section.levels[localIndex] = (short) ((currentStoredLevel & ~0xFF) | (toPropagate & 0xFF)); -+ updatedPositions.putAndMoveToLast(CoordinateUtils.getChunkKey(offX, offZ), (byte) toPropagate); - - // queue next - if (toPropagate > 1) { -@@ -1113,7 +1126,7 @@ public abstract class ThreadedTicketLevelPropagator { - // the child bitset is 4x4, so we just shift each line by 4 - // add the propagation bitset offset to each line to make it easy to OR it into the propagation queue value - final long childPropagation = -- ((bitsetLine1 >>> (start)) << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = -1 -+ ((bitsetLine1 >>> (start)) << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = -1 - ((bitsetLine2 >>> (start + 8)) << (4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = 0 - ((bitsetLine3 >>> (start + (8 + 8))) << (4 + 4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); // z = 1 - -@@ -1123,7 +1136,7 @@ public abstract class ThreadedTicketLevelPropagator { - queue = this.resizeIncreaseQueue(); - } - queue[queueLength++] = -- ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | -+ ((long) (offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | - ((toPropagate & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | - childPropagation; //(ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); - continue; -@@ -1144,18 +1157,19 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1144,6 +1155,7 @@ public abstract class ThreadedTicketLevelPropagator { final int decodeOffsetZ = -this.encodeOffsetZ; final int encodeOffset = this.coordinateOffset; final int sectionOffset = this.sectionIndexOffset; @@ -192,34 +59,7 @@ index 01598347ea545f2ff2ac337086345d7369a64520..f7b995909cb580d03de3bf9ee20a6e36 final Long2ByteLinkedOpenHashMap updatedPositions = this.updatedPositions; - while (queueReadIndex < queueLength) { - final long queueValue = queue[queueReadIndex++]; - -- final int posX = ((int)queueValue & (COORDINATE_SIZE - 1)) + decodeOffsetX; -- final int posZ = (((int)queueValue >>> COORDINATE_BITS) & (COORDINATE_SIZE - 1)) + decodeOffsetZ; -- final int propagatedLevel = ((int)queueValue >>> (COORDINATE_BITS + COORDINATE_BITS)) & (LEVEL_COUNT - 1); -+ final int posX = ((int) queueValue & (COORDINATE_SIZE - 1)) + decodeOffsetX; -+ final int posZ = (((int) queueValue >>> COORDINATE_BITS) & (COORDINATE_SIZE - 1)) + decodeOffsetZ; -+ final int propagatedLevel = ((int) queueValue >>> (COORDINATE_BITS + COORDINATE_BITS)) & (LEVEL_COUNT - 1); - // note: the above code requires coordinate bits * 2 < 32 - // bitset is 16 bits -- int propagateDirectionBitset = (int)(queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); -+ int propagateDirectionBitset = (int) (queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); - - // this bitset represents the values that we have not propagated to - // this bitset lets us determine what directions the neighbours we set should propagate to, in most cases -@@ -1170,8 +1184,8 @@ public abstract class ThreadedTicketLevelPropagator { - // must guarantee that either we propagate everything in 1 radius or we partially propagate for 1 radius - // but the rest not propagated are already handled - long currentPropagation = ~( -- // z = -1 -- (1L << ((2 - 1) | ((2 - 1) << 3))) | -+ // z = -1 -+ (1L << ((2 - 1) | ((2 - 1) << 3))) | - (1L << ((2 + 0) | ((2 - 1) << 3))) | - (1L << ((2 + 1) | ((2 - 1) << 3))) | - -@@ -1227,7 +1241,7 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1227,7 +1239,7 @@ public abstract class ThreadedTicketLevelPropagator { final long bitsetLine3 = currentPropagation & (7L << (start + (8 + 8))); // now try to propagate @@ -228,50 +68,3 @@ index 01598347ea545f2ff2ac337086345d7369a64520..f7b995909cb580d03de3bf9ee20a6e36 // lower 8 bits are current level, next upper 7 bits are source level, next 1 bit is updated source flag final short currentStoredLevel = section.levels[localIndex]; -@@ -1244,7 +1258,7 @@ public abstract class ThreadedTicketLevelPropagator { - increaseQueue = this.resizeIncreaseQueue(); - } - increaseQueue[increaseQueueLength++] = -- ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | -+ ((long) (offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | - ((currentLevel & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | - (FLAG_RECHECK_LEVEL | (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS))); - continue; -@@ -1255,8 +1269,8 @@ public abstract class ThreadedTicketLevelPropagator { - //currentPropagation ^= (bitsetLine1 | bitsetLine2 | bitsetLine3); - - // update level -- section.levels[localIndex] = (short)((currentStoredLevel & ~0xFF)); -- updatedPositions.putAndMoveToLast(CoordinateUtils.getChunkKey(offX, offZ), (byte)0); -+ section.levels[localIndex] = (short) ((currentStoredLevel & ~0xFF)); -+ updatedPositions.putAndMoveToLast(CoordinateUtils.getChunkKey(offX, offZ), (byte) 0); - - if (sourceLevel != 0) { - // re-propagate source -@@ -1265,7 +1279,7 @@ public abstract class ThreadedTicketLevelPropagator { - increaseQueue = this.resizeIncreaseQueue(); - } - increaseQueue[increaseQueueLength++] = -- ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | -+ ((long) (offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | - ((sourceLevel & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | - (FLAG_WRITE_LEVEL | (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS))); - } -@@ -1276,7 +1290,7 @@ public abstract class ThreadedTicketLevelPropagator { - // the child bitset is 4x4, so we just shift each line by 4 - // add the propagation bitset offset to each line to make it easy to OR it into the propagation queue value - final long childPropagation = -- ((bitsetLine1 >>> (start)) << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = -1 -+ ((bitsetLine1 >>> (start)) << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = -1 - ((bitsetLine2 >>> (start + 8)) << (4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = 0 - ((bitsetLine3 >>> (start + (8 + 8))) << (4 + 4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); // z = 1 - -@@ -1286,7 +1300,7 @@ public abstract class ThreadedTicketLevelPropagator { - queue = this.resizeDecreaseQueue(); - } - queue[queueLength++] = -- ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | -+ ((long) (offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | - ((toPropagate & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | - (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); //childPropagation; - continue; From bd3eb337d04abb9d4ed2ca69058fb819647b26e8 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Thu, 1 May 2025 08:53:32 +0800 Subject: [PATCH 30/45] fix async chunk sending buffer (#301) * fix Async chunk sending buffer size * cleanup * synchronized write nonEmptyBlockCount * increase buffer initial capacity * fix block entity map data race --------- Co-authored-by: Taiyou06 --- .../features/0132-Async-chunk-sending.patch | 154 +++++++++++++++++- .../features/0157-Optimise-chunkUnloads.patch | 6 +- .../features/0034-Async-chunk-sending.patch | 124 ++++++++++++++ 3 files changed, 275 insertions(+), 9 deletions(-) create mode 100644 leaf-server/paper-patches/features/0034-Async-chunk-sending.patch diff --git a/leaf-server/minecraft-patches/features/0132-Async-chunk-sending.patch b/leaf-server/minecraft-patches/features/0132-Async-chunk-sending.patch index e8b716e1..76b36fb2 100644 --- a/leaf-server/minecraft-patches/features/0132-Async-chunk-sending.patch +++ b/leaf-server/minecraft-patches/features/0132-Async-chunk-sending.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Async chunk sending diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -index a35e9fae8f8da0c42f0616c4f78dc396492673aa..af49117695c0785033b984ff91550e2fccbbc5e6 100644 +index a35e9fae8f8da0c42f0616c4f78dc396492673aa..c0f14d2bc30a186511bafddc2e80f29b686887c5 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -@@ -411,19 +411,91 @@ public final class RegionizedPlayerChunkLoader { +@@ -411,19 +411,98 @@ public final class RegionizedPlayerChunkLoader { this.delayedTicketOps.addLast(op); } @@ -29,7 +29,6 @@ index a35e9fae8f8da0c42f0616c4f78dc396492673aa..af49117695c0785033b984ff91550e2f + // Already in our sent list - silently return instead of throwing an exception + return; + } -+ + // Get the chunk now, as we need it for both sync and async paths + final LevelChunk chunk = ((ChunkSystemLevel) this.world).moonrise$getFullChunkIfLoaded(chunkX, chunkZ); + if (chunk == null) { @@ -37,13 +36,12 @@ index a35e9fae8f8da0c42f0616c4f78dc396492673aa..af49117695c0785033b984ff91550e2f + this.sentChunks.remove(chunkKey); + return; + } -+ + + // Try to mark the chunk as received by this player + try { + // This part needs to remain on the main thread as it affects shared state + ((ChunkSystemServerLevel) this.world).moonrise$getChunkTaskScheduler().chunkHolderManager + .getChunkHolder(chunkX, chunkZ).vanillaChunkHolder.moonrise$addReceivedChunk(this.player); - + // Call onChunkWatch on the main thread as it might affect server state PlatformHooks.get().onChunkWatch(this.world, chunk, this.player); - PlayerChunkSender.sendChunk(this.player.connection, this.world, chunk); @@ -58,6 +56,13 @@ index a35e9fae8f8da0c42f0616c4f78dc396492673aa..af49117695c0785033b984ff91550e2f + // Check if async chunk sending is enabled + if (org.dreeam.leaf.config.modules.async.AsyncChunkSend.enabled) { + // Async implementation ++ var heightmaps = new net.minecraft.nbt.CompoundTag(); ++ for (var entry : chunk.getHeightmaps()) { ++ if (entry.getKey().sendToClient()) { ++ heightmaps.put(entry.getKey().getSerializationKey(), new net.minecraft.nbt.LongArrayTag(entry.getValue().getRawData())); ++ } ++ } ++ var blockEntities = chunk.blockEntities.values().toArray(new net.minecraft.world.level.block.entity.BlockEntity[0]); + net.minecraft.Util.backgroundExecutor().execute(() -> { + try { + final net.minecraft.server.network.ServerGamePacketListenerImpl connection = this.player.connection; @@ -66,7 +71,9 @@ index a35e9fae8f8da0c42f0616c4f78dc396492673aa..af49117695c0785033b984ff91550e2f + // Create the packet with anti-xray control flag + final net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket packet = new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket( + chunk, serverLevel.getLightEngine(), null, null, -+ serverLevel.chunkPacketBlockController.shouldModify(this.player, chunk) ++ serverLevel.chunkPacketBlockController.shouldModify(this.player, chunk), ++ heightmaps, ++ blockEntities + ); + + // Let the main thread handle the anti-xray processing @@ -106,3 +113,138 @@ index a35e9fae8f8da0c42f0616c4f78dc396492673aa..af49117695c0785033b984ff91550e2f private void sendUnloadChunk(final int chunkX, final int chunkZ) { if (!this.sentChunks.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { +diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java +index 9e321ef1c3d5803519b243685f4ee598dc0cf640..c981801171307e571388e6e810840ae2525c5d10 100644 +--- a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java ++++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java +@@ -26,6 +26,7 @@ public class ClientboundLevelChunkPacketData { + private static final int TWO_MEGABYTES = 2097152; + private final CompoundTag heightmaps; + private final byte[] buffer; ++ private final int bufferLength; + private final List blockEntitiesData; + // Paper start - Handle oversized block entities in chunks + private final java.util.List> extraPackets = new java.util.ArrayList<>(); +@@ -52,6 +53,7 @@ public class ClientboundLevelChunkPacketData { + } + + this.buffer = new byte[calculateChunkSize(levelChunk)]; ++ this.bufferLength = this.buffer.length; // Leaf + // Paper start - Anti-Xray - Add chunk packet info + if (chunkPacketInfo != null) { + chunkPacketInfo.setBuffer(this.buffer); +@@ -74,6 +76,51 @@ public class ClientboundLevelChunkPacketData { + } + } + ++ // Leaf start - Async chunk sending ++ public ClientboundLevelChunkPacketData( ++ LevelChunk levelChunk, ++ io.papermc.paper.antixray.ChunkPacketInfo chunkPacketInfo, ++ CompoundTag heightmaps, ++ BlockEntity[] blockEntities ++ ) { ++ this.heightmaps = heightmaps; ++ var buffer = new FriendlyByteBuf(Unpooled.buffer(calculateChunkSize(levelChunk) + 128)); ++ var sections = levelChunk.getSections(); ++ var sectionLength = sections.length; ++ for (int i = 0; i < sectionLength; i++) { ++ LevelChunkSection section = sections[i]; ++ synchronized (section.getStates()) { ++ buffer.writeShort(section.nonEmptyBlockCount()); ++ section.getStates().write(buffer, chunkPacketInfo, i); ++ } ++ section.getBiomes().write(buffer, null, i); ++ } ++ this.buffer = buffer.array(); ++ this.bufferLength = buffer.writerIndex(); ++ ++ // Paper start - Anti-Xray - Add chunk packet info ++ if (chunkPacketInfo != null) { ++ chunkPacketInfo.setBuffer(this.buffer); ++ chunkPacketInfo.setLength(this.bufferLength); ++ } ++ this.blockEntitiesData = Lists.newArrayList(); ++ int totalTileEntities = 0; // Paper - Handle oversized block entities in chunks ++ ++ for (BlockEntity blockEntity : blockEntities) { ++ // Paper start - Handle oversized block entities in chunks ++ if (++totalTileEntities > BLOCK_ENTITY_LIMIT) { ++ net.minecraft.network.protocol.Packet packet = blockEntity.getUpdatePacket(); ++ if (packet != null) { ++ this.extraPackets.add(packet); ++ continue; ++ } ++ } ++ // Paper end - Handle oversized block entities in chunks ++ this.blockEntitiesData.add(ClientboundLevelChunkPacketData.BlockEntityInfo.create(blockEntity)); ++ } ++ } ++ // Leaf end - Async chunk sending ++ + public ClientboundLevelChunkPacketData(RegistryFriendlyByteBuf buffer, int x, int z) { + this.heightmaps = buffer.readNbt(); + if (this.heightmaps == null) { +@@ -84,6 +131,7 @@ public class ClientboundLevelChunkPacketData { + throw new RuntimeException("Chunk Packet trying to allocate too much memory on read."); + } else { + this.buffer = new byte[varInt]; ++ this.bufferLength = this.buffer.length; // Leaf + buffer.readBytes(this.buffer); + this.blockEntitiesData = ClientboundLevelChunkPacketData.BlockEntityInfo.LIST_STREAM_CODEC.decode(buffer); + } +@@ -92,8 +140,8 @@ public class ClientboundLevelChunkPacketData { + + public void write(RegistryFriendlyByteBuf buffer) { + buffer.writeNbt(this.heightmaps); +- buffer.writeVarInt(this.buffer.length); +- buffer.writeBytes(this.buffer); ++ buffer.writeVarInt(this.bufferLength); // Leaf ++ buffer.writeBytes(this.buffer, 0, this.bufferLength); // Leaf + ClientboundLevelChunkPacketData.BlockEntityInfo.LIST_STREAM_CODEC.encode(buffer, this.blockEntitiesData); + } + +diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java +index 8578d1f78ddd1bb75f3230f04bfaa35af9f5f822..4eeb4967120b1c2cf13d2b4a8c07175fb4d98012 100644 +--- a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java +@@ -45,6 +45,26 @@ public class ClientboundLevelChunkWithLightPacket implements Packet chunkPacketInfo = modifyBlocks ? chunk.getLevel().chunkPacketBlockController.getChunkPacketInfo(this, chunk) : null; // Paper - Ant-Xray ++ this.chunkData = new ClientboundLevelChunkPacketData(chunk, chunkPacketInfo, heightmaps, blockEntities); // Paper - Anti-Xray ++ this.lightData = new ClientboundLightUpdatePacketData(pos, lightEngine, skyLight, blockLight); ++ chunk.getLevel().chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks ++ } ++ // Leaf end - Async chunk sending ++ + private ClientboundLevelChunkWithLightPacket(RegistryFriendlyByteBuf buffer) { + this.x = buffer.readInt(); + this.z = buffer.readInt(); +diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java +index b8ac6a9ba7b56ccd034757f7d135d272b8e69e90..f85c1c3ab8cc0d0dafa6df8536318e3443265d2b 100644 +--- a/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -49,6 +49,8 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ + } + // Paper end - block counting + ++ public final int nonEmptyBlockCount() { return this.nonEmptyBlockCount; } // Leaf ++ + private LevelChunkSection(LevelChunkSection section) { + this.nonEmptyBlockCount = section.nonEmptyBlockCount; + this.tickingBlockCount = section.tickingBlockCount; diff --git a/leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch b/leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch index 61b8d35b..03d616cf 100644 --- a/leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch +++ b/leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch @@ -159,7 +159,7 @@ index 4ca68a903e67606fc4ef0bfa9862a73797121c8b..bed3a64388bb43e47c2ba4e67f7dde5b public static final class SaveState { diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java -index b8ac6a9ba7b56ccd034757f7d135d272b8e69e90..dc158e981199b507531af810ff9ced3ca717e39e 100644 +index f85c1c3ab8cc0d0dafa6df8536318e3443265d2b..449f180c49f191786c5f253baf04ddd9b72835f7 100644 --- a/net/minecraft/world/level/chunk/LevelChunkSection.java +++ b/net/minecraft/world/level/chunk/LevelChunkSection.java @@ -24,6 +24,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ @@ -170,7 +170,7 @@ index b8ac6a9ba7b56ccd034757f7d135d272b8e69e90..dc158e981199b507531af810ff9ced3c // Paper start - block counting private static final it.unimi.dsi.fastutil.shorts.ShortArrayList FULL_LIST = new it.unimi.dsi.fastutil.shorts.ShortArrayList(16*16*16); -@@ -135,6 +136,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ +@@ -137,6 +138,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ // Paper end - block counting public BlockState setBlockState(int x, int y, int z, BlockState state, boolean useLocks) { @@ -178,7 +178,7 @@ index b8ac6a9ba7b56ccd034757f7d135d272b8e69e90..dc158e981199b507531af810ff9ced3c BlockState blockState; if (useLocks) { blockState = this.states.getAndSet(x, y, z, state); -@@ -328,7 +330,32 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ +@@ -330,7 +332,32 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ this.biomes = palettedContainer; } diff --git a/leaf-server/paper-patches/features/0034-Async-chunk-sending.patch b/leaf-server/paper-patches/features/0034-Async-chunk-sending.patch new file mode 100644 index 00000000..b52674a1 --- /dev/null +++ b/leaf-server/paper-patches/features/0034-Async-chunk-sending.patch @@ -0,0 +1,124 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Sun, 27 Apr 2025 21:19:30 +0800 +Subject: [PATCH] Async chunk sending + + +diff --git a/src/main/java/io/papermc/paper/antixray/BitStorageReader.java b/src/main/java/io/papermc/paper/antixray/BitStorageReader.java +index c27703775c845a8b0bd1b0cb8f05eb736d8a813c..7e72c910232b85b411ecba1fa0acdf7f81c85418 100644 +--- a/src/main/java/io/papermc/paper/antixray/BitStorageReader.java ++++ b/src/main/java/io/papermc/paper/antixray/BitStorageReader.java +@@ -8,11 +8,18 @@ public final class BitStorageReader { + private int longInBufferIndex; + private int bitInLongIndex; + private long current; ++ private int length; // Leaf + + public void setBuffer(byte[] buffer) { + this.buffer = buffer; + } + ++ // Leaf start ++ public void setLength(int length) { ++ this.length = length; ++ } ++ // Leaf end ++ + public void setBits(int bits) { + this.bits = bits; + mask = (1 << bits) - 1; +@@ -25,7 +32,7 @@ public final class BitStorageReader { + } + + private void init() { +- if (buffer.length > longInBufferIndex + 7) { ++ if (length > longInBufferIndex + 7) { // Leaf + current = ((((long) buffer[longInBufferIndex]) << 56) + | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48) + | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40) +diff --git a/src/main/java/io/papermc/paper/antixray/BitStorageWriter.java b/src/main/java/io/papermc/paper/antixray/BitStorageWriter.java +index 83412e0ddaade11eb7ac7b41bb8ae5b085802775..d521bd2275152575f5fe5038a817471026abccda 100644 +--- a/src/main/java/io/papermc/paper/antixray/BitStorageWriter.java ++++ b/src/main/java/io/papermc/paper/antixray/BitStorageWriter.java +@@ -9,11 +9,18 @@ public final class BitStorageWriter { + private int bitInLongIndex; + private long current; + private boolean dirty; ++ private int length; // Leaf + + public void setBuffer(byte[] buffer) { + this.buffer = buffer; + } + ++ // Leaf start ++ public void setLength(int length) { ++ this.length = length; ++ } ++ // Leaf end ++ + public void setBits(int bits) { + this.bits = bits; + mask = (1L << bits) - 1; +@@ -26,7 +33,7 @@ public final class BitStorageWriter { + } + + private void init() { +- if (buffer.length > longInBufferIndex + 7) { ++ if (length > longInBufferIndex + 7) { // Leaf + current = ((((long) buffer[longInBufferIndex]) << 56) + | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48) + | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40) +@@ -41,7 +48,7 @@ public final class BitStorageWriter { + } + + public void flush() { +- if (dirty && buffer.length > longInBufferIndex + 7) { ++ if (dirty && length > longInBufferIndex + 7) { // Leaf + buffer[longInBufferIndex] = (byte) (current >> 56 & 0xff); + buffer[longInBufferIndex + 1] = (byte) (current >> 48 & 0xff); + buffer[longInBufferIndex + 2] = (byte) (current >> 40 & 0xff); +diff --git a/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java b/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java +index ee2d3a54d760f9c26542eab03c51651a30e279a0..bbf338e5e98a7b7d1c7cd986333df03f4aad4bb1 100644 +--- a/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java ++++ b/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java +@@ -227,6 +227,8 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo + boolean[] obfuscateTemp = null; + bitStorageReader.setBuffer(chunkPacketInfoAntiXray.getBuffer()); + bitStorageWriter.setBuffer(chunkPacketInfoAntiXray.getBuffer()); ++ bitStorageReader.setLength(chunkPacketInfoAntiXray.getLength()); ++ bitStorageWriter.setLength(chunkPacketInfoAntiXray.getLength()); + int numberOfBlocks = presetBlockStateBits.length; + // Keep the lambda expressions as simple as possible. They are used very frequently. + LayeredIntSupplier random = numberOfBlocks == 1 ? (() -> 0) : engineMode == EngineMode.OBFUSCATE_LAYER ? new LayeredIntSupplier() { +diff --git a/src/main/java/io/papermc/paper/antixray/ChunkPacketInfo.java b/src/main/java/io/papermc/paper/antixray/ChunkPacketInfo.java +index a33a4d45d478ededff27244fcb910d3f369f2151..26f0e4b43c2adc27e0ca2bafb7a1bab3fcf8f6cc 100644 +--- a/src/main/java/io/papermc/paper/antixray/ChunkPacketInfo.java ++++ b/src/main/java/io/papermc/paper/antixray/ChunkPacketInfo.java +@@ -13,6 +13,7 @@ public class ChunkPacketInfo { + private final int[] indexes; + private final Object[][] presetValues; + private byte[] buffer; ++ private int length; // Leaf + + public ChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) { + this.chunkPacket = chunkPacket; +@@ -36,8 +37,19 @@ public class ChunkPacketInfo { + return buffer; + } + ++ // Leaf start ++ public int getLength() { ++ return length; ++ } ++ // Leaf end ++ + public void setBuffer(byte[] buffer) { + this.buffer = buffer; ++ this.length = buffer.length; // Leaf ++ } ++ ++ public void setLength(int length) { ++ this.length = length; // Leaf + } + + public int getBits(int chunkSectionIndex) { From 2b13b38801ad8bec0d1b6bce008cd81bdfd4cbd9 Mon Sep 17 00:00:00 2001 From: Dreeam-qwq <61569423+Dreeam-qwq@users.noreply.github.com> Date: Thu, 1 May 2025 01:41:29 +0000 Subject: [PATCH 31/45] Updated Upstream (Gale) Upstream has released updates that appear to apply and compile correctly Gale Changes: Dreeam-qwq/Gale@d5d63524 Updated Upstream (Paper) --- gradle.properties | 2 +- .../0005-Purpur-Server-Paper-Changes.patch | 16 ++++++++-------- .../features/0007-Remove-Timings.patch | 4 ++-- .../features/0010-Leaves-Protocol-Core.patch | 6 +++--- .../features/0011-Leaves-Replay-Mod-API.patch | 8 ++++---- .../features/0015-Including-5s-in-getTPS.patch | 4 ++-- .../features/0019-Matter-Secure-Seed.patch | 4 ++-- ...ace-world-map-with-optimized-collection.patch | 4 ++-- ...ter-CraftServer-getworlds-list-creation.patch | 4 ++-- 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/gradle.properties b/gradle.properties index 0815ac26..66d5cc50 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ group=cn.dreeam.leaf mcVersion=1.21.4 version=1.21.4-R0.1-SNAPSHOT -galeCommit=f57773d224f54c451a9862b2ea22d5634f4078a4 +galeCommit=d5d63524e02c6b7ff448ede990beb9edca897485 org.gradle.configuration-cache=true org.gradle.caching=true diff --git a/leaf-server/paper-patches/features/0005-Purpur-Server-Paper-Changes.patch b/leaf-server/paper-patches/features/0005-Purpur-Server-Paper-Changes.patch index b2758401..52c3af06 100644 --- a/leaf-server/paper-patches/features/0005-Purpur-Server-Paper-Changes.patch +++ b/leaf-server/paper-patches/features/0005-Purpur-Server-Paper-Changes.patch @@ -558,10 +558,10 @@ index 94ca0407303c4493ab4928b12ec6ecc75aaca549..f2d87c12dd19210ce7e2147fada5c101 + // Purpur end - OfflinePlayer API } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 1b1720343d3b07a73490f66520c7df12005adbcc..28c6d96686a01e7f102edbef6b685bdbb80aa014 100644 +index b51e07e06d9b593f890bf97e39c2c5672536d313..b9ba938330caead8cf41accdc76932c5e5a5dd91 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -429,6 +429,20 @@ public final class CraftServer implements Server { +@@ -430,6 +430,20 @@ public final class CraftServer implements Server { this.paperPluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(this, this.commandMap, pluginManager); this.pluginManager.paperPluginManager = this.paperPluginManager; // Paper end @@ -582,7 +582,7 @@ index 1b1720343d3b07a73490f66520c7df12005adbcc..28c6d96686a01e7f102edbef6b685bdb CraftRegistry.setMinecraftRegistry(console.registryAccess()); -@@ -1090,6 +1104,7 @@ public final class CraftServer implements Server { +@@ -1091,6 +1105,7 @@ public final class CraftServer implements Server { org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot this.console.paperConfigurations.reloadConfigs(this.console); this.console.galeConfigurations.reloadConfigs(this.console); // Gale - Gale configuration @@ -590,7 +590,7 @@ index 1b1720343d3b07a73490f66520c7df12005adbcc..28c6d96686a01e7f102edbef6b685bdb for (ServerLevel world : this.console.getAllLevels()) { // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) -@@ -1105,6 +1120,7 @@ public final class CraftServer implements Server { +@@ -1106,6 +1121,7 @@ public final class CraftServer implements Server { } } world.spigotConfig.init(); // Spigot @@ -598,7 +598,7 @@ index 1b1720343d3b07a73490f66520c7df12005adbcc..28c6d96686a01e7f102edbef6b685bdb } Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper -@@ -1123,6 +1139,7 @@ public final class CraftServer implements Server { +@@ -1124,6 +1140,7 @@ public final class CraftServer implements Server { io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper org.galemc.gale.command.GaleCommands.registerCommands(this.console); // Gale - Gale commands - register commands this.spark.registerCommandBeforePlugins(this); // Paper - spark @@ -606,7 +606,7 @@ index 1b1720343d3b07a73490f66520c7df12005adbcc..28c6d96686a01e7f102edbef6b685bdb this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); -@@ -1654,6 +1671,60 @@ public final class CraftServer implements Server { +@@ -1655,6 +1672,60 @@ public final class CraftServer implements Server { return true; } @@ -667,7 +667,7 @@ index 1b1720343d3b07a73490f66520c7df12005adbcc..28c6d96686a01e7f102edbef6b685bdb @Override public List getRecipesFor(ItemStack result) { Preconditions.checkArgument(result != null, "ItemStack cannot be null"); -@@ -3062,6 +3133,18 @@ public final class CraftServer implements Server { +@@ -3067,6 +3138,18 @@ public final class CraftServer implements Server { } // Gale end - Gale configuration - API @@ -686,7 +686,7 @@ index 1b1720343d3b07a73490f66520c7df12005adbcc..28c6d96686a01e7f102edbef6b685bdb @Override public void restart() { CraftServer.this.restart(); -@@ -3359,4 +3442,18 @@ public final class CraftServer implements Server { +@@ -3364,4 +3447,18 @@ public final class CraftServer implements Server { return MinecraftServer.lastTickOversleepTime; } // Gale end - YAPFA - last tick time - API diff --git a/leaf-server/paper-patches/features/0007-Remove-Timings.patch b/leaf-server/paper-patches/features/0007-Remove-Timings.patch index 04d531b9..2394488b 100644 --- a/leaf-server/paper-patches/features/0007-Remove-Timings.patch +++ b/leaf-server/paper-patches/features/0007-Remove-Timings.patch @@ -47,10 +47,10 @@ index 097500a59336db1bbfffcd1aa4cff7a8586e46ec..f06076864582ed153c6154fd7f3e9101 @Override diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 1816ec002c91d108529e9b1dcd7b5f0a7fbc52ef..aa6f0588f282e3152ef9639d6fb2b8cd1d54bdb7 100644 +index b9ba938330caead8cf41accdc76932c5e5a5dd91..ddf9a5d3af7ebe8e5c69d7dcb8a1c3a28d8178e3 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1049,10 +1049,8 @@ public final class CraftServer implements Server { +@@ -1050,10 +1050,8 @@ public final class CraftServer implements Server { commands.performCommand(results, commandLine, commandLine, true); } catch (CommandException ex) { this.pluginManager.callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerCommandException(ex, target, sender, args))); // Paper diff --git a/leaf-server/paper-patches/features/0010-Leaves-Protocol-Core.patch b/leaf-server/paper-patches/features/0010-Leaves-Protocol-Core.patch index 686d82ac..576636f1 100644 --- a/leaf-server/paper-patches/features/0010-Leaves-Protocol-Core.patch +++ b/leaf-server/paper-patches/features/0010-Leaves-Protocol-Core.patch @@ -12,10 +12,10 @@ Original project: https://github.com/LeavesMC/Leaves Commit: 41476d86922416c45f703df2871890831fc42bb5 diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index aa6f0588f282e3152ef9639d6fb2b8cd1d54bdb7..514c2d414ac59c71929a7686204465c72d122513 100644 +index ddf9a5d3af7ebe8e5c69d7dcb8a1c3a28d8178e3..3fabeaa91832eacc103416682aec3ce6121858fb 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -508,6 +508,7 @@ public final class CraftServer implements Server { +@@ -509,6 +509,7 @@ public final class CraftServer implements Server { this.potionBrewer = new io.papermc.paper.potion.PaperPotionBrewer(console); // Paper - custom potion mixes datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper this.spark = new io.papermc.paper.SparksFly(this); // Paper - spark @@ -23,7 +23,7 @@ index aa6f0588f282e3152ef9639d6fb2b8cd1d54bdb7..514c2d414ac59c71929a7686204465c7 } public boolean getCommandBlockOverride(String command) { -@@ -1140,6 +1141,7 @@ public final class CraftServer implements Server { +@@ -1141,6 +1142,7 @@ public final class CraftServer implements Server { org.purpurmc.purpur.PurpurConfig.registerCommands(); // Purpur - Purpur config files this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); diff --git a/leaf-server/paper-patches/features/0011-Leaves-Replay-Mod-API.patch b/leaf-server/paper-patches/features/0011-Leaves-Replay-Mod-API.patch index b0ae8b06..923206cc 100644 --- a/leaf-server/paper-patches/features/0011-Leaves-Replay-Mod-API.patch +++ b/leaf-server/paper-patches/features/0011-Leaves-Replay-Mod-API.patch @@ -27,10 +27,10 @@ index 4c003acccdd2dd17918b15316001e52e7670123e..780f3a48152fef6a06dc67bf7fbd1965 HandlerList handlers = event.getHandlers(); RegisteredListener[] listeners = handlers.getRegisteredListeners(); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index eec03c52172b8250c18318094093c752da97dbe6..f57e12a72cabe279a6a465f4397b44621a2d5cb0 100644 +index 3fabeaa91832eacc103416682aec3ce6121858fb..7ca77be262e6c8c9882db42295a42487b672d43e 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -322,6 +322,8 @@ public final class CraftServer implements Server { +@@ -323,6 +323,8 @@ public final class CraftServer implements Server { private final io.papermc.paper.threadedregions.scheduler.FoliaAsyncScheduler asyncScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaAsyncScheduler(); private final io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler globalRegionScheduler = new io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler(); @@ -39,7 +39,7 @@ index eec03c52172b8250c18318094093c752da97dbe6..f57e12a72cabe279a6a465f4397b4462 @Override public final io.papermc.paper.threadedregions.scheduler.RegionScheduler getRegionScheduler() { return this.regionizedScheduler; -@@ -410,7 +412,7 @@ public final class CraftServer implements Server { +@@ -411,7 +413,7 @@ public final class CraftServer implements Server { public CraftServer(DedicatedServer console, PlayerList playerList) { this.console = console; this.playerList = (DedicatedPlayerList) playerList; @@ -48,7 +48,7 @@ index eec03c52172b8250c18318094093c752da97dbe6..f57e12a72cabe279a6a465f4397b4462 @Override public CraftPlayer apply(ServerPlayer player) { return player.getBukkitEntity(); -@@ -3456,4 +3458,11 @@ public final class CraftServer implements Server { +@@ -3461,4 +3463,11 @@ public final class CraftServer implements Server { return getServer().lagging; } // Purpur end - Lagging threshold diff --git a/leaf-server/paper-patches/features/0015-Including-5s-in-getTPS.patch b/leaf-server/paper-patches/features/0015-Including-5s-in-getTPS.patch index 41236a04..7a0b40ba 100644 --- a/leaf-server/paper-patches/features/0015-Including-5s-in-getTPS.patch +++ b/leaf-server/paper-patches/features/0015-Including-5s-in-getTPS.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Including 5s in getTPS() diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index f57e12a72cabe279a6a465f4397b44621a2d5cb0..01bfd366dd5e20464dd1bda0e05f80f8ae9c1687 100644 +index 7ca77be262e6c8c9882db42295a42487b672d43e..99a3c2d19bb263f499a5be1e9f603ad55b60ebe7 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -3180,6 +3180,8 @@ public final class CraftServer implements Server { +@@ -3185,6 +3185,8 @@ public final class CraftServer implements Server { @Override public double[] getTPS() { diff --git a/leaf-server/paper-patches/features/0019-Matter-Secure-Seed.patch b/leaf-server/paper-patches/features/0019-Matter-Secure-Seed.patch index 6eae85d5..d1d016dd 100644 --- a/leaf-server/paper-patches/features/0019-Matter-Secure-Seed.patch +++ b/leaf-server/paper-patches/features/0019-Matter-Secure-Seed.patch @@ -32,10 +32,10 @@ index de8b9048c8395c05b8688bc9d984b8ad680f15b3..fab62216edd7181585fbf1e5cd9870e8 @Override diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index ef0c0374261bc4d673e0b8150e268328125e3542..2f030eae4603f9a33fbc8d0e9b400479ab336e32 100644 +index 99a3c2d19bb263f499a5be1e9f603ad55b60ebe7..eacc39145d22b9afac773623497f8b18836914e8 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -1414,7 +1414,11 @@ public final class CraftServer implements Server { +@@ -1415,7 +1415,11 @@ public final class CraftServer implements Server { registryAccess = levelDataAndDimensions.dimensions().dimensionsRegistryAccess(); } else { LevelSettings levelSettings; diff --git a/leaf-server/paper-patches/features/0022-Replace-world-map-with-optimized-collection.patch b/leaf-server/paper-patches/features/0022-Replace-world-map-with-optimized-collection.patch index 7b753545..1d4e39f7 100644 --- a/leaf-server/paper-patches/features/0022-Replace-world-map-with-optimized-collection.patch +++ b/leaf-server/paper-patches/features/0022-Replace-world-map-with-optimized-collection.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Replace world map with optimized collection diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 2f030eae4603f9a33fbc8d0e9b400479ab336e32..116c842ef64835152a662476b6c33152360c1a4a 100644 +index eacc39145d22b9afac773623497f8b18836914e8..d0d2ceb45218e6eec14ac9963bf816ff17053af8 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -285,7 +285,7 @@ public final class CraftServer implements Server { +@@ -286,7 +286,7 @@ public final class CraftServer implements Server { private final StructureManager structureManager; protected final DedicatedServer console; protected final DedicatedPlayerList playerList; diff --git a/leaf-server/paper-patches/features/0027-Faster-CraftServer-getworlds-list-creation.patch b/leaf-server/paper-patches/features/0027-Faster-CraftServer-getworlds-list-creation.patch index c9e72749..791803c0 100644 --- a/leaf-server/paper-patches/features/0027-Faster-CraftServer-getworlds-list-creation.patch +++ b/leaf-server/paper-patches/features/0027-Faster-CraftServer-getworlds-list-creation.patch @@ -8,10 +8,10 @@ replacing ArrayList with Fastutil ObjectArrayList brings about 40% performance improvement in benchmark. diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 116c842ef64835152a662476b6c33152360c1a4a..fcedf7af3fff97821cbb7ee2e0fef070659deb5a 100644 +index d0d2ceb45218e6eec14ac9963bf816ff17053af8..dd38057071de1ecce2e420bd0696a134190a09c1 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -993,7 +993,7 @@ public final class CraftServer implements Server { +@@ -994,7 +994,7 @@ public final class CraftServer implements Server { @Override public List getWorlds() { From 3b8ec970c81e59d5ece55e417462bda2c0965192 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Thu, 1 May 2025 22:29:42 +0800 Subject: [PATCH 32/45] optimize async target finding (#303) * optimize async target finding * fix canContinueToUse * fix data race * fix TemptGoal start before search entity * fix AvoidEntityGoal doesn't create path * fix inactiveTick tickingTarget * default queueSize to 0 * default queueSize to 4096 --- .../features/0154-Async-target-finding.patch | 1023 +++++++++-------- .../leaf/async/ai/AsyncGoalExecutor.java | 71 +- .../dreeam/leaf/async/ai/AsyncGoalThread.java | 37 + .../java/org/dreeam/leaf/async/ai/Waker.java | 17 + .../modules/async/AsyncTargetFinding.java | 38 +- .../dreeam/leaf/util/queue/SpscIntQueue.java | 63 + 6 files changed, 725 insertions(+), 524 deletions(-) create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/util/queue/SpscIntQueue.java diff --git a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch index 82309c9a..63d3116c 100644 --- a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch @@ -5,18 +5,25 @@ Subject: [PATCH] Async target finding diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 24926aa7ed5c78b235659daf18b224b14beb744c..2603f5ca5e5f3fd86af76aec7e16039bf9c9292d 100644 +index 24926aa7ed5c78b235659daf18b224b14beb744c..98af1ad020a003db66d7319f33d43deec315aec5 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -1088,6 +1088,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop entitiesWithScheduledTasks = java.util.concurrent.ConcurrentHashMap.newKeySet(); // SparklyPaper - skip EntityScheduler's executeTick checks if there isn't any tasks to be run (concurrent because plugins may schedule tasks async) + public java.util.concurrent.Semaphore serverLevelTickingSemaphore = null; // SparklyPaper - parallel world ticking ++ @Nullable public org.dreeam.leaf.async.ai.AsyncGoalThread asyncGoalThread; // Leaf - Async target finding + + public static S spin(Function threadFunction) { + ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system +@@ -1088,6 +1089,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop realPlayers; // Leaves - skip -+ public List asyncAITasks = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); // Leaf ++ public final @Nullable org.dreeam.leaf.async.ai.AsyncGoalExecutor asyncGoalExecutor; // Leaf - Async target finding public LevelChunk getChunkIfLoaded(int x, int z) { return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately -@@ -861,6 +871,21 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -335,6 +345,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); + } + ++ // Leaf start - Async target finding ++ public final void leafMidTickTasks() { ++ if (this.asyncGoalExecutor != null) this.asyncGoalExecutor.midTick(); ++ } ++ // Leaf end - Async target finding ++ + @Override + public final ChunkAccess moonrise$syncLoadNonFull(final int chunkX, final int chunkZ, final net.minecraft.world.level.chunk.status.ChunkStatus status) { + return this.moonrise$getChunkTaskScheduler().syncLoadNonFull(chunkX, chunkZ, status); +@@ -711,6 +727,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle + this.realPlayers = Lists.newArrayList(); // Leaves - skip + this.tickExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new org.dreeam.leaf.async.world.SparklyPaperServerLevelTickExecutorThreadFactory(getWorld().getName())); // SparklyPaper - parallel world ticking ++ // Leaf start - Async target finding ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ this.asyncGoalExecutor = new org.dreeam.leaf.async.ai.AsyncGoalExecutor(server.asyncGoalThread, this); ++ } else { ++ this.asyncGoalExecutor = null; ++ } ++ // Leaf end - Async target finding + } + + // Paper start +@@ -855,12 +878,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + this.moonrise$midTickTasks(); // Paper - rewrite chunk system + // Gale end - Airplane - remove lambda from ticking guard - copied from guardEntityTick ++ this.leafMidTickTasks(); // Leaf - Async target finding + } + } + } } ); this.tickBlockEntities(); -+ // Leaf start - Async target finding -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled -+ && org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled -+ ) { -+ final var tasks = this.asyncAITasks; -+ this.asyncAITasks = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); -+ org.dreeam.leaf.async.ai.AsyncGoalExecutor.runTasks(tasks); -+ } else -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ final var tasks = this.asyncAITasks; -+ this.asyncAITasks = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); -+ org.dreeam.leaf.async.ai.AsyncGoalExecutor.EXECUTOR.execute( -+ () -> org.dreeam.leaf.async.ai.AsyncGoalExecutor.runTasks(tasks)); -+ } -+ // Leaf end - Async target finding ++ if (this.asyncGoalExecutor != null) this.asyncGoalExecutor.unpark(); // Leaf - Async target finding } // Paper - rewrite chunk system +@@ -1329,6 +1354,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) + // Paper end - rewrite chunk system + ++ this.leafMidTickTasks(); // Leaf - Async target finding + } + + private void tickBlock(BlockPos pos, Block block) { +@@ -1345,6 +1371,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) + // Paper end - rewrite chunk system + ++ this.leafMidTickTasks(); // Leaf - Async target finding + } + + // Paper start - log detailed entity tick information diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 90879616842cc61d15854b07f56f6fcb89f11074..0ae36986492d98f46ccf409f6b71ed9cfca488e7 100644 +index 90879616842cc61d15854b07f56f6fcb89f11074..f114a8f5143799d72e36e0a535888c5fb25213e1 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -243,6 +243,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess protected Vec3 stuckSpeedMultiplier = Vec3.ZERO; @Nullable private Entity.RemovalReason removalReason; -+ private final java.util.concurrent.atomic.AtomicBoolean isRemoved = new java.util.concurrent.atomic.AtomicBoolean(false); // Leaf - atomic removal check ++ private volatile boolean isRemoved = false; // Leaf - volatile removal check public static final float DEFAULT_BB_WIDTH = 0.6F; public static final float DEFAULT_BB_HEIGHT = 1.8F; public float moveDist; @@ -93,13 +150,13 @@ index 90879616842cc61d15854b07f56f6fcb89f11074..0ae36986492d98f46ccf409f6b71ed9c public final boolean isRemoved() { - return this.removalReason != null; -+ // Leaf start - atomic removal check ++ // Leaf start - volatile removal check + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ return this.isRemoved.getAcquire(); ++ return this.isRemoved; + } else { + return this.removalReason != null; + } -+ // Leaf end - atomic removal check ++ // Leaf end - volatile removal check } @Nullable @@ -107,11 +164,11 @@ index 90879616842cc61d15854b07f56f6fcb89f11074..0ae36986492d98f46ccf409f6b71ed9c final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers if (this.removalReason == null) { this.removalReason = removalReason; -+ // Leaf start - atomic removal check -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ this.isRemoved.setRelease(true); ++ // Leaf start - volatile removal check ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled && this.removalReason != null) { ++ this.isRemoved = true; + } -+ // Leaf end - atomic removal check ++ // Leaf end - volatile removal check } if (this.removalReason.shouldDestroy()) { @@ -119,28 +176,86 @@ index 90879616842cc61d15854b07f56f6fcb89f11074..0ae36986492d98f46ccf409f6b71ed9c public void unsetRemoved() { this.removalReason = null; -+ // Leaf start - atomic removal check ++ // Leaf start - volatile removal check + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ this.isRemoved.setRelease(false); ++ this.isRemoved = false; + } -+ // Leaf end - atomic removal check ++ // Leaf end - volatile removal check } // Paper start - Folia schedulers +diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java +index 05d5cde42b7011091ef4ee874c0d9d5586ae3f10..bf021604e0c345ef72cbc55d4f8fe996d2d561f7 100644 +--- a/net/minecraft/world/entity/Mob.java ++++ b/net/minecraft/world/entity/Mob.java +@@ -144,6 +144,10 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + private float restrictRadius = -1.0F; + public boolean aware = true; // CraftBukkit + public int ticksSinceLastInteraction; // Purpur - Entity lifespan ++ public boolean tickingTarget; // Leaf - Async target finding ++ public final org.dreeam.leaf.async.ai.Waker getGoalCtx() { ++ return tickingTarget ? this.targetSelector.ctx : this.goalSelector.ctx; ++ } + + protected Mob(EntityType entityType, Level level) { + super(entityType, level); +@@ -225,12 +229,21 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + } + // Paper end - Skip AI during inactive ticks for non-aware mobs + boolean isThrottled = org.dreeam.leaf.config.modules.opt.ThrottleInactiveGoalSelectorTick.enabled && _pufferfish_inactiveTickDisableCounter++ % 20 != 0; // Pufferfish - throttle inactive goal selector ticking ++ this.tickingTarget = false; // Leaf + if (this.goalSelector.inactiveTick(this.activatedPriority, true) && !isThrottled) { // Pufferfish - pass activated priroity // Pufferfish - throttle inactive goal selector ticking + this.goalSelector.tick(); + } ++ this.tickingTarget = true; // Leaf + if (this.targetSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priority + this.targetSelector.tick(); + } ++ // Leaf start - Async target finding ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ if (this.targetSelector.ctxGoals != null || this.goalSelector.ctxGoals != null) { ++ ((ServerLevel) this.level()).asyncGoalExecutor.submit(this.getId()); ++ } ++ } ++ // Leaf start - Async target finding + } + // Paper end + +@@ -914,17 +927,28 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + // Paper end - Allow nerfed mobs to jump and float + this.sensing.tick(); + int i = this.tickCount + this.getId(); ++ // Leaf start - Async target finding + if (i % 2 != 0 && this.tickCount > 1) { ++ this.tickingTarget = true; + if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking + this.targetSelector.tickRunningGoals(false); ++ this.tickingTarget = false; + if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking + this.goalSelector.tickRunningGoals(false); + } else { ++ this.tickingTarget = true; + if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking + this.targetSelector.tick(); ++ this.tickingTarget = false; + if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking + this.goalSelector.tick(); + } ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ if (this.targetSelector.ctxGoals != null || this.goalSelector.ctxGoals != null) { ++ ((ServerLevel) this.level()).asyncGoalExecutor.submit(this.getId()); ++ } ++ } ++ // Leaf end - Async target finding + + this.navigation.tick(); + this.customServerAiStep((ServerLevel)this.level()); diff --git a/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java b/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java -index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..77e51ffce5afc9089fd3a2e382cdfde7a6cf1c35 100644 +index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..5239f4dfeaed8adccceb2a6d6578308261f46628 100644 --- a/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java +++ b/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java -@@ -65,17 +65,32 @@ public class AvoidEntityGoal extends Goal { - this(mob, entityClassToAvoid, livingEntity -> true, maxDistance, walkSpeedModifier, sprintSpeedModifier, predicateOnAvoidEntity); - } +@@ -67,15 +67,24 @@ public class AvoidEntityGoal extends Goal { -+ // Leaf start - Async Avoid Entity Finding -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ // Leaf end - Async Avoid Entity Finding -+ @Override public boolean canUse() { - this.toAvoid = getServerLevel(this.mob) @@ -153,11 +268,11 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..77e51ffce5afc9089fd3a2e382cdfde7 - this.mob.getZ() - ); + // Leaf start - Async Avoid Entity Finding -+ if (poll()) { -+ return true; -+ } + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { -+ getNearestEntityAsync(); ++ if (!poll()) { ++ getNearestEntityAsync(); ++ return false; ++ } + } else { + this.toAvoid = getServerLevel(this.mob) + .getNearestEntity( @@ -173,63 +288,48 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..77e51ffce5afc9089fd3a2e382cdfde7 if (this.toAvoid == null) { return false; } else { -@@ -91,6 +106,62 @@ public class AvoidEntityGoal extends Goal { +@@ -91,6 +100,47 @@ public class AvoidEntityGoal extends Goal { } } + // Leaf start - Async Avoid Entity Finding + private boolean poll() { -+ LivingEntity t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return false; -+ } ++ if (!(this.mob.getGoalCtx().result() instanceof LivingEntity entity)) { return false; } + var serverLevel = getServerLevel(this.mob); -+ if (serverLevel == null || !t.isAlive() || !this.avoidEntityTargeting.test(serverLevel, this.mob, t)) { ++ if (serverLevel == null || !entity.isAlive() || !this.avoidEntityTargeting.test(serverLevel, this.mob, entity)) { + return false; + } -+ this.toAvoid = (T) t; ++ this.toAvoid = (T) entity; + return true; + } + + private void getNearestEntityAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final var mob = this.mob; -+ -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ ++ final var ctx = mob.getGoalCtx(); ++ if (!ctx.state) return; + final double x = mob.getX(); + final double y = mob.getEyeY(); + final double z = mob.getZ(); + final var serverLevel = getServerLevel(mob); + final var avoidClass = this.avoidClass; + final var bound = mob.getBoundingBox().inflate(this.maxDist, 3.0, this.maxDist); -+ final var pendingTarget = this.pendingTarget; -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob == null || mob.isRemoved() || !mob.isAlive() || mob.level() != serverLevel) { + return; + } -+ var avoid = serverLevel.getNearestEntity( -+ serverLevel.getEntitiesOfClass(avoidClass, bound, LivingEntity::isAlive), ++ ctx.result = serverLevel.getNearestEntity( ++ serverLevel.getEntitiesOfClass(avoidClass, bound, livingEntity -> true), + avoidEntityTargeting, + mob, + x, + y, + z + ); -+ pendingTarget.setRelease(avoid); + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async Avoid Entity Finding + @@ -237,19 +337,16 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..77e51ffce5afc9089fd3a2e382cdfde7 public boolean canContinueToUse() { return !this.pathNav.isDone(); diff --git a/net/minecraft/world/entity/ai/goal/BegGoal.java b/net/minecraft/world/entity/ai/goal/BegGoal.java -index 28ef40e8a645989ea181297069cf2bbe571f3082..56a5fda5f1e2482e1c89c7c2f9c34ea85562702e 100644 +index 28ef40e8a645989ea181297069cf2bbe571f3082..5f1a0ccaed29324a24bfbea96c9dc3adfeb11ad5 100644 --- a/net/minecraft/world/entity/ai/goal/BegGoal.java +++ b/net/minecraft/world/entity/ai/goal/BegGoal.java -@@ -27,8 +27,74 @@ public class BegGoal extends Goal { +@@ -27,8 +27,54 @@ public class BegGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.LOOK)); } + // Leaf start - Async Target Finding -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); + protected boolean poll() { -+ Player t = pendingTarget.getAndSet(null); ++ if (!(this.wolf.getGoalCtx().result() instanceof Player t)) { return false; } + if (t == null) { + return false; + } @@ -262,27 +359,12 @@ index 28ef40e8a645989ea181297069cf2bbe571f3082..56a5fda5f1e2482e1c89c7c2f9c34ea8 + } + + private void findTargetAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ -+ // Capture mutable state to avoid race conditions + final Wolf wolf = this.wolf; -+ -+ // Safety check -+ if (wolf == null || wolf.isRemoved() || !wolf.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ -+ final double x = wolf.getX(); -+ final double y = wolf.getEyeY(); -+ final double z = wolf.getZ(); ++ final var ctx = wolf.getGoalCtx(); ++ if (!ctx.state) return; + final ServerLevel serverLevel = getServerLevel(wolf); + final TargetingConditions begTargeting = this.begTargeting; -+ final var pendingTarget = this.pendingTarget; -+ -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (wolf.level() != serverLevel || wolf.isRemoved() || !wolf.isAlive()) { + return; @@ -290,14 +372,12 @@ index 28ef40e8a645989ea181297069cf2bbe571f3082..56a5fda5f1e2482e1c89c7c2f9c34ea8 + + var player = serverLevel.getNearestPlayer(begTargeting, wolf); + if (player != null && playerHoldingInteresting(player)) { -+ pendingTarget.setRelease(player); ++ ctx.result = player; + } + } catch (Exception e) { -+ LOGGER.error("Exception getting player", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async Target Finding + @@ -307,7 +387,7 @@ index 28ef40e8a645989ea181297069cf2bbe571f3082..56a5fda5f1e2482e1c89c7c2f9c34ea8 + if (poll()) { + return true; + } -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchPlayer) { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + findTargetAsync(); + return false; + } @@ -315,7 +395,7 @@ index 28ef40e8a645989ea181297069cf2bbe571f3082..56a5fda5f1e2482e1c89c7c2f9c34ea8 this.player = this.level.getNearestPlayer(this.begTargeting, this.wolf); return this.player != null && this.playerHoldingInteresting(this.player); } -@@ -59,14 +125,16 @@ public class BegGoal extends Goal { +@@ -59,14 +105,16 @@ public class BegGoal extends Goal { this.lookTime--; } @@ -382,42 +462,27 @@ index 9954f49bc364969c7ccb37f4186fa2ab8710f6ae..b0a93215a7d5e8fe58331f261d7b4f87 + // Leaf end - Async search block } diff --git a/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java b/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java -index 1fe4d1877ed52017d4d22ddb9b77f78e2b93dff9..1f478e26a743fe6fdeccbe2c1f0efed0387fcb1e 100644 +index 1fe4d1877ed52017d4d22ddb9b77f78e2b93dff9..ec52e6c8110876eb915485c54a8102c4afe460f6 100644 --- a/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java +++ b/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java -@@ -23,8 +23,71 @@ public class FollowBoatGoal extends Goal { +@@ -23,8 +23,54 @@ public class FollowBoatGoal extends Goal { this.mob = mob; } + // Leaf start - Async Target Finding -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); -+ + private boolean poll() { -+ Player t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return false; -+ } ++ if (!(this.mob.getGoalCtx().result() instanceof Player t)) { return false; } + var serverLevel = getServerLevel(this.mob); + return serverLevel != null && t.isAlive() && !t.isSpectator() && (Mth.abs(t.xxa) > 0.0F || Mth.abs(t.zza) > 0.0F); + } + + private void findTargetAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final PathfinderMob mob = this.mob; -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ ++ final var ctx = mob.getGoalCtx(); ++ if (!ctx.state) return; + final var bound = mob.getBoundingBox().inflate(5.0); + final var serverLevel = getServerLevel(mob); -+ final var pendingTarget = this.pendingTarget; -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob.level() != serverLevel || mob.isRemoved() || !mob.isAlive()) { + return; @@ -428,16 +493,14 @@ index 1fe4d1877ed52017d4d22ddb9b77f78e2b93dff9..1f478e26a743fe6fdeccbe2c1f0efed0 + Entity controllingPassenger = abstractBoat.getControllingPassenger(); + if (controllingPassenger instanceof Player player + && (Mth.abs(player.xxa) > 0.0F || Mth.abs(player.zza) > 0.0F)) { -+ pendingTarget.setRelease(player); ++ ctx.result = player; + break; + } + } + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async Target Finding @Override @@ -457,7 +520,7 @@ index 1fe4d1877ed52017d4d22ddb9b77f78e2b93dff9..1f478e26a743fe6fdeccbe2c1f0efed0 List entitiesOfClass = this.mob.level().getEntitiesOfClass(AbstractBoat.class, this.mob.getBoundingBox().inflate(5.0)); boolean flag = false; -@@ -37,7 +100,7 @@ public class FollowBoatGoal extends Goal { +@@ -37,7 +83,7 @@ public class FollowBoatGoal extends Goal { } } @@ -467,19 +530,11 @@ index 1fe4d1877ed52017d4d22ddb9b77f78e2b93dff9..1f478e26a743fe6fdeccbe2c1f0efed0 @Override diff --git a/net/minecraft/world/entity/ai/goal/FollowMobGoal.java b/net/minecraft/world/entity/ai/goal/FollowMobGoal.java -index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..48708da53db05f2668b4e1eb52cd3effa49ea983 100644 +index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..0d123e6bac5748b6c5efd76f4c1e086cbf749783 100644 --- a/net/minecraft/world/entity/ai/goal/FollowMobGoal.java +++ b/net/minecraft/world/entity/ai/goal/FollowMobGoal.java -@@ -36,8 +36,23 @@ public class FollowMobGoal extends Goal { - } - } +@@ -38,6 +38,15 @@ public class FollowMobGoal extends Goal { -+ // Leaf start - Async Follow Mob Finding -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ // Leaf end - Async Follow Mob Finding -+ @Override public boolean canUse() { + // Leaf start - Async Follow Mob Finding @@ -494,16 +549,13 @@ index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..48708da53db05f2668b4e1eb52cd3eff List entitiesOfClass = this.mob.level().getEntitiesOfClass(Mob.class, this.mob.getBoundingBox().inflate(this.areaSize), this.followPredicate); if (!entitiesOfClass.isEmpty()) { for (Mob mob : entitiesOfClass) { -@@ -51,6 +66,62 @@ public class FollowMobGoal extends Goal { +@@ -51,6 +60,45 @@ public class FollowMobGoal extends Goal { return false; } + // Leaf start - Async Follow Mob Finding + private boolean poll() { -+ var t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return false; -+ } ++ if (!(this.mob.getGoalCtx().result() instanceof Mob t)) { return false; } + var serverLevel = getServerLevel(this.mob); + if (serverLevel == null || !t.isAlive() || t.isInvisible()) { + return false; @@ -513,25 +565,13 @@ index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..48708da53db05f2668b4e1eb52cd3eff + } + + private void getFollowingMobAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final var mob = this.mob; -+ -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ -+ final double x = mob.getX(); -+ final double y = mob.getEyeY(); -+ final double z = mob.getZ(); ++ final var ctx = this.mob.getGoalCtx(); ++ if (!ctx.state) return; + final var serverLevel = getServerLevel(mob); + final var bound = this.mob.getBoundingBox().inflate(this.areaSize); + final var followPredicate = this.followPredicate; -+ final var pendingTarget = this.pendingTarget; -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob == null || mob.isRemoved() || !mob.isAlive() || mob.level() != serverLevel) { + return; @@ -540,17 +580,15 @@ index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..48708da53db05f2668b4e1eb52cd3eff + if (!entitiesOfClass.isEmpty()) { + for (final Mob follow : entitiesOfClass) { + if (!follow.isInvisible()) { -+ pendingTarget.setRelease(follow); ++ ctx.result = follow; + return; + } + } + } + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async Follow Mob Finding + @@ -558,22 +596,16 @@ index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..48708da53db05f2668b4e1eb52cd3eff public boolean canContinueToUse() { return this.followingMob != null && !this.navigation.isDone() && this.mob.distanceToSqr(this.followingMob) > this.stopDistance * this.stopDistance; diff --git a/net/minecraft/world/entity/ai/goal/FollowParentGoal.java b/net/minecraft/world/entity/ai/goal/FollowParentGoal.java -index 3093f03d4f298bf39fec8bad2b6c22518774aea8..7d97345e8a5c630bf53cce3bd543e46e0b596237 100644 +index 3093f03d4f298bf39fec8bad2b6c22518774aea8..ee42674761653cacc5045fcb60e314fbe25f390f 100644 --- a/net/minecraft/world/entity/ai/goal/FollowParentGoal.java +++ b/net/minecraft/world/entity/ai/goal/FollowParentGoal.java -@@ -19,11 +19,84 @@ public class FollowParentGoal extends Goal { +@@ -19,11 +19,68 @@ public class FollowParentGoal extends Goal { this.speedModifier = speedModifier; } + // Leaf start - Async Target Finding -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); + protected boolean poll() { -+ var t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return false; -+ } ++ if (!(this.animal.getGoalCtx().result() instanceof Animal t)) { return false; } + var serverLevel = getServerLevel(animal); + if (serverLevel == null || !t.isAlive() || animal.distanceToSqr(t) < 9.0) { + return false; @@ -583,22 +615,14 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..7d97345e8a5c630bf53cce3bd543e46e + } + + private void findTargetAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final Animal animal = this.animal; -+ if (animal == null || animal.isRemoved() || !animal.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ ++ final var ctx = animal.getGoalCtx(); ++ if (!ctx.state) return; + final var targetType = animal.getClass(); + final var bound = animal.getBoundingBox().inflate(8.0, 4.0, 8.0); + final var serverLevel = getServerLevel(animal); + final var pos = animal.position(); -+ final var pendingTarget = this.pendingTarget; -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (animal.level() != serverLevel || animal.isRemoved() || !animal.isAlive()) { + return; @@ -618,14 +642,12 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..7d97345e8a5c630bf53cce3bd543e46e + } + } + if (target != null) { -+ pendingTarget.setRelease(target); ++ ctx.result = target; + } + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async Target Finding + @@ -646,7 +668,7 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..7d97345e8a5c630bf53cce3bd543e46e List entitiesOfClass = this.animal .level() .getEntitiesOfClass((Class)this.animal.getClass(), this.animal.getBoundingBox().inflate(8.0, 4.0, 8.0)); -@@ -43,6 +116,7 @@ public class FollowParentGoal extends Goal { +@@ -43,6 +100,7 @@ public class FollowParentGoal extends Goal { if (animal == null) { return false; } else if (d < 9.0) { @@ -654,23 +676,151 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..7d97345e8a5c630bf53cce3bd543e46e return false; } else { this.parent = animal; +diff --git a/net/minecraft/world/entity/ai/goal/GoalSelector.java b/net/minecraft/world/entity/ai/goal/GoalSelector.java +index e82e32407cec6109b9c3b0106295217f4a3f4aa2..32463fae3d3a58e71c4aae2d81b2a1b60fee7d69 100644 +--- a/net/minecraft/world/entity/ai/goal/GoalSelector.java ++++ b/net/minecraft/world/entity/ai/goal/GoalSelector.java +@@ -26,6 +26,13 @@ public class GoalSelector { + private final ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet goalTypes = new ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet<>(Goal.Flag.class); // Paper - remove streams from GoalSelector + private int curRate; // Paper - EAR 2 + ++ // Leaf start - Async target finding ++ public WrappedGoal @org.jetbrains.annotations.Nullable[] ctxGoals = null; ++ private int ctxIndex = 0; ++ private int ctxState = 0; ++ public final org.dreeam.leaf.async.ai.Waker ctx = new org.dreeam.leaf.async.ai.Waker(); ++ // Leaf end - Async target finding ++ + public void addGoal(int priority, Goal goal) { + this.availableGoals.add(new WrappedGoal(priority, goal)); + } +@@ -85,7 +92,107 @@ public class GoalSelector { + return true; + } + ++ public final boolean poll() { ++ if (this.ctxGoals == null || ctx.wake != null) { ++ return false; ++ } ++ if (ctxState == 0) { ++ while (ctxIndex < this.ctxGoals.length) { ++ WrappedGoal goal = this.ctxGoals[ctxIndex]; ++ // entity tempt ++ if (goal.isRunning() && (goalContainsAnyFlags(goal, this.goalTypes) || !goal.canContinueToUse())) { ++ ctx.state = false; ++ if (ctx.wake != null) { ++ return true; ++ } ++ goal.stop(); ++ } ++ ++ ctxIndex++; ++ ctx.state = true; ++ } ++ ++ this.lockedFlags.entrySet().removeIf(entry -> !entry.getValue().isRunning()); ++ ++ ctxIndex = 0; ++ ctx.state = true; ++ ctxState = 1; ++ } ++ if (ctxState == 1) { ++ while (ctxIndex < this.ctxGoals.length) { ++ WrappedGoal goal = this.ctxGoals[ctxIndex]; ++ // entity and block ++ if (!goal.isRunning() ++ && !goalContainsAnyFlags(goal, this.goalTypes) ++ && goalCanBeReplacedForAllFlags(goal, this.lockedFlags) ++ ) { ++ if (goal.canUse()) { ++ long flagIterator = goal.getFlags().getBackingSet(); ++ int wrappedGoalSize = goal.getFlags().size(); ++ for (int i = 0; i < wrappedGoalSize; ++i) { ++ final Goal.Flag flag = GOAL_FLAG_VALUES[Long.numberOfTrailingZeros(flagIterator)]; ++ flagIterator ^= ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit(flagIterator); ++ WrappedGoal wrappedGoal1 = this.lockedFlags.getOrDefault(flag, NO_GOAL); ++ wrappedGoal1.stop(); ++ this.lockedFlags.put(flag, goal); ++ } ++ ++ goal.start(); ++ } ++ ctx.state = false; ++ if (ctx.wake != null) { ++ return true; ++ } ++ } ++ // entity alert other ++ if (!ctx.state) { ++ switch (goal.getGoal()) { ++ case net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal t -> t.poll(); ++ case net.minecraft.world.entity.ai.goal.target.ResetUniversalAngerTargetGoal t -> t.poll(); ++ default -> {} ++ } ++ } ++ ctxIndex++; ++ ctx.state = true; ++ } ++ ++ ctxIndex = 0; ++ ctx.state = true; ++ ctxState = 2; ++ } ++ if (ctxState == 2) { ++ while (ctxIndex < this.ctxGoals.length) { ++ WrappedGoal goal = this.ctxGoals[ctxIndex]; ++ if (goal.isRunning()) { ++ goal.tick(); ++ } ++ ctxIndex++; ++ } ++ ++ ctxGoals = null; ++ ctxState = 0; ++ ctxIndex = 0; ++ ctx.state = true; ++ } ++ return false; ++ } ++ ++ public final void wake() { ++ Runnable wake = ctx.wake; ++ if (wake != null) { ++ wake.run(); ++ } ++ ctx.wake = null; ++ } ++ + public void tick() { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ if (this.ctxGoals == null) { ++ this.ctxGoals = this.availableGoals.toArray(new WrappedGoal[0]); ++ } ++ return; ++ } ++ + for (WrappedGoal wrappedGoal : this.availableGoals) { + if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.goalTypes) || !wrappedGoal.canContinueToUse())) { // Paper - Perf: optimize goal types by removing streams + wrappedGoal.stop(); +@@ -94,6 +201,7 @@ public class GoalSelector { + + this.lockedFlags.entrySet().removeIf(entry -> !entry.getValue().isRunning()); + ++ + for (WrappedGoal wrappedGoalx : this.availableGoals) { + // Paper start + if (!wrappedGoalx.isRunning() && !goalContainsAnyFlags(wrappedGoalx, this.goalTypes) && goalCanBeReplacedForAllFlags(wrappedGoalx, this.lockedFlags) && wrappedGoalx.canUse()) { diff --git a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java -index be59d0c27a83b329ec3f97c029cfb9c114e22472..23c5e933ccb98a38228810953a7e2447d5115c84 100644 +index be59d0c27a83b329ec3f97c029cfb9c114e22472..239567f1ac82e17ec8f0624f1a33c1faa7ad13fe 100644 --- a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java +++ b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java -@@ -20,19 +20,110 @@ public class LlamaFollowCaravanGoal extends Goal { +@@ -20,19 +20,94 @@ public class LlamaFollowCaravanGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.MOVE)); } + // Leaf start - Async Target Finding -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); + private @javax.annotation.Nullable Llama poll() { -+ var t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return null; -+ } ++ if (!(this.llama.getGoalCtx().result() instanceof Llama t)) { return null; } + var serverLevel = getServerLevel(this.llama); + if (serverLevel == null || !t.isAlive()) { + return null; @@ -679,21 +829,13 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..23c5e933ccb98a38228810953a7e2447 + } + + private void findTargetAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final Llama llama = this.llama; -+ if (llama == null || llama.isRemoved() || !llama.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ ++ final var ctx = llama.getGoalCtx(); ++ if (!ctx.state) return; + final var bound = llama.getBoundingBox().inflate(9.0, 4.0, 9.0); + final var serverLevel = getServerLevel(llama); + final var pos = llama.position(); -+ final var pendingTarget = this.pendingTarget; -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (llama.level() != serverLevel || llama.isRemoved() || !llama.isAlive()) { + return; @@ -730,14 +872,12 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..23c5e933ccb98a38228810953a7e2447 + } + } + if (target != null) { -+ pendingTarget.setRelease(target); ++ ctx.result = target; + } + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async Target Finding @Override @@ -771,7 +911,7 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..23c5e933ccb98a38228810953a7e2447 if (llama1.inCaravan() && !llama1.hasCaravanTail()) { double d1 = this.llama.distanceToSqr(llama1); if (!(d1 > d)) { -@@ -45,6 +136,7 @@ public class LlamaFollowCaravanGoal extends Goal { +@@ -45,6 +120,7 @@ public class LlamaFollowCaravanGoal extends Goal { if (llama == null) { for (Entity entityx : entities) { Llama llama1 = (Llama)entityx; @@ -779,7 +919,7 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..23c5e933ccb98a38228810953a7e2447 if (llama1.isLeashed() && !llama1.hasCaravanTail()) { double d1 = this.llama.distanceToSqr(llama1); if (!(d1 > d)) { -@@ -54,6 +146,7 @@ public class LlamaFollowCaravanGoal extends Goal { +@@ -54,6 +130,7 @@ public class LlamaFollowCaravanGoal extends Goal { } } } @@ -788,10 +928,10 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..23c5e933ccb98a38228810953a7e2447 if (llama == null) { return false; diff --git a/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java b/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java -index 6463c3c9b08d6058f2843c225b08a40fc30a960b..0f39eca165f3eae1b05d1e0548f63467bc21bbf5 100644 +index 6463c3c9b08d6058f2843c225b08a40fc30a960b..819f7947b0c9c546cf45b2effe72cfb597a99ce6 100644 --- a/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java +++ b/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java -@@ -48,32 +48,123 @@ public class LookAtPlayerGoal extends Goal { +@@ -48,32 +48,94 @@ public class LookAtPlayerGoal extends Goal { @Override public boolean canUse() { @@ -802,19 +942,34 @@ index 6463c3c9b08d6058f2843c225b08a40fc30a960b..0f39eca165f3eae1b05d1e0548f63467 + if (this.mob.getRandom().nextFloat() >= this.probability) { return false; -- } else { -- if (this.mob.getTarget() != null) { -- this.lookAt = this.mob.getTarget(); + } -+ if (this.lookAtType == Player.class) { -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchPlayer) { -+ getLookAsync(); -+ return false; - } -+ } else if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ if (this.mob.getTarget() != null) { ++ this.lookAt = this.mob.getTarget(); ++ } ++ ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + getLookAsync(); + return false; + } ++ ++ ServerLevel serverLevel = getServerLevel(this.mob); ++ if (this.lookAtType == Player.class) { ++ this.lookAt = serverLevel.getNearestPlayer(this.lookAtContext, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); + } else { +- if (this.mob.getTarget() != null) { +- this.lookAt = this.mob.getTarget(); +- } ++ this.lookAt = serverLevel.getNearestEntity( ++ this.mob ++ .level() ++ .getEntitiesOfClass(this.lookAtType, this.mob.getBoundingBox().inflate(this.lookDistance, 3.0, this.lookDistance), livingEntity -> true), ++ this.lookAtContext, ++ this.mob, ++ this.mob.getX(), ++ this.mob.getEyeY(), ++ this.mob.getZ() ++ ); ++ } - ServerLevel serverLevel = getServerLevel(this.mob); - if (this.lookAtType == Player.class) { @@ -831,40 +986,14 @@ index 6463c3c9b08d6058f2843c225b08a40fc30a960b..0f39eca165f3eae1b05d1e0548f63467 - this.mob.getZ() - ); - } -+ if (this.mob.getTarget() != null) { -+ this.lookAt = this.mob.getTarget(); -+ } -+ -+ ServerLevel serverLevel = getServerLevel(this.mob); -+ if (this.lookAtType == Player.class) { -+ this.lookAt = serverLevel.getNearestPlayer(this.lookAtContext, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); -+ } else { -+ this.lookAt = serverLevel.getNearestEntity( -+ this.mob -+ .level() -+ .getEntitiesOfClass(this.lookAtType, this.mob.getBoundingBox().inflate(this.lookDistance, 3.0, this.lookDistance), livingEntity -> true), -+ this.lookAtContext, -+ this.mob, -+ this.mob.getX(), -+ this.mob.getEyeY(), -+ this.mob.getZ() -+ ); -+ } -+ + return this.lookAt != null; + // Leaf end - Async look finding + } - return this.lookAt != null; + // Leaf start - Async look finding -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; + protected boolean poll() { -+ LivingEntity t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return false; -+ } ++ if (!(this.mob.getGoalCtx().result() instanceof LivingEntity t)) { return false; } + if (this.mob.getTarget() != null) { + this.lookAt = this.mob.getTarget(); + return true; @@ -872,47 +1001,32 @@ index 6463c3c9b08d6058f2843c225b08a40fc30a960b..0f39eca165f3eae1b05d1e0548f63467 + ServerLevel serverLevel = getServerLevel(this.mob); + if (!t.isAlive() || !this.lookAtContext.test(serverLevel, this.mob, t)) { + return false; -+ } + } + this.lookAt = t; + return true; + } + + protected void getLookAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; - } -+ + final var mob = this.mob; -+ -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ ++ final var ctx = mob.getGoalCtx(); ++ if (!ctx.state) return; + final double x = mob.getX(); + final double y = mob.getEyeY(); + final double z = mob.getZ(); + final var serverLevel = getServerLevel(mob); + final var bound = mob.getBoundingBox().inflate(this.lookDistance, 3.0, this.lookDistance); -+ final var target = mob.getTarget(); + final var lookAtContext = this.lookAtContext; + final var lookAtType = this.lookAtType; -+ final var pendingTarget = this.pendingTarget; -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob == null || !mob.isAlive() || mob.level() != serverLevel) { + return; + } -+ if (target != null) { -+ pendingTarget.setRelease(target); -+ return; -+ } + + if (lookAtType == Player.class) { -+ var result = serverLevel.getNearestPlayer(lookAtContext, mob, x, y, z); -+ pendingTarget.setRelease(result); ++ ctx.result = serverLevel.getNearestPlayer(lookAtContext, mob, x, y, z); + } else { -+ var result = serverLevel.getNearestEntity( ++ ctx.result = serverLevel.getNearestEntity( + serverLevel + .getEntitiesOfClass(lookAtType, bound, livingEntity -> true), + lookAtContext, @@ -921,36 +1035,27 @@ index 6463c3c9b08d6058f2843c225b08a40fc30a960b..0f39eca165f3eae1b05d1e0548f63467 + y, + z + ); -+ pendingTarget.setRelease(result); + } + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; } + // Leaf end - Async look finding @Override public boolean canContinueToUse() { diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..6ff92396c6c97f4185e287f04eb197842d8b5fec 100644 +index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..99e6e7bac2b5af1b1abb1de6b51aed1f44e5e8b0 100644 --- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -@@ -41,8 +41,90 @@ public abstract class MoveToBlockGoal extends Goal { +@@ -41,8 +41,69 @@ public abstract class MoveToBlockGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.JUMP)); } + // Leaf start - Async search block -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingBlock = new java.util.concurrent.atomic.AtomicReference<>(null); -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; + protected boolean poll() { -+ BlockPos blockPos1 = pendingBlock.getAndSet(null); -+ if (blockPos1 == null) { -+ return false; -+ } ++ if (!(this.mob.getGoalCtx().result() instanceof BlockPos blockPos1)) { return false; } + if (!this.mob.level().hasChunkAt(blockPos1) + || !this.mob.isWithinRestriction(blockPos1) + || !this.isValidTarget(this.mob.level(), blockPos1)) { @@ -962,23 +1067,10 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..6ff92396c6c97f4185e287f04eb19784 + } + + protected void getBlockAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final var mob = this.mob; -+ -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ -+ final double x = mob.getX(); -+ final double y = mob.getEyeY(); -+ final double z = mob.getZ(); ++ final var ctx = mob.getGoalCtx(); ++ if (!ctx.state) return; + final var serverLevel = getServerLevel(mob); -+ -+ final java.util.concurrent.atomic.AtomicReference pendingBlock = this.pendingBlock; + final TypeToCheck ty = this.typeToCheck(); + final net.minecraft.world.level.block.Block toRemove; + if (this instanceof RemoveBlockGoal removeBlockGoal) { @@ -992,18 +1084,16 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..6ff92396c6c97f4185e287f04eb19784 + final BlockPos blockPos = mob.blockPosition(); + final float restrictRadius = mob.getRestrictRadius(); + final BlockPos restrictCenter = mob.getRestrictCenter(); -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob == null || !mob.isAlive() || mob.level() != serverLevel) { + return; + } -+ findNearestBlockAsync(pendingBlock, ty, toRemove, mob, serverLevel, verticalSearchStart, searchRange, verticalSearchRange, blockPos, restrictRadius, restrictCenter); ++ findNearestBlockAsync(ctx, ty, toRemove, mob, serverLevel, verticalSearchStart, searchRange, verticalSearchRange, blockPos, restrictRadius, restrictCenter); + } catch (Exception e) { -+ LOGGER.error("Exception getting block", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting block", e); + } -+ }); ++ }; + } + + protected enum TypeToCheck { @@ -1029,7 +1119,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..6ff92396c6c97f4185e287f04eb19784 if (this.nextStartTick > 0) { this.nextStartTick--; return false; -@@ -109,6 +191,10 @@ public abstract class MoveToBlockGoal extends Goal { +@@ -109,6 +170,10 @@ public abstract class MoveToBlockGoal extends Goal { } protected boolean findNearestBlock() { @@ -1040,12 +1130,12 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..6ff92396c6c97f4185e287f04eb19784 int i = this.searchRange; int i1 = this.verticalSearchRange; BlockPos blockPos = this.mob.blockPosition(); -@@ -133,5 +219,104 @@ public abstract class MoveToBlockGoal extends Goal { +@@ -133,5 +198,104 @@ public abstract class MoveToBlockGoal extends Goal { return false; } + protected static boolean findNearestBlockAsync( -+ final java.util.concurrent.atomic.AtomicReference pendingBlock, ++ final org.dreeam.leaf.async.ai.Waker ctx, + final TypeToCheck ty, + @org.jetbrains.annotations.Nullable final net.minecraft.world.level.block.Block toRemove, + final PathfinderMob mob, @@ -1066,7 +1156,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..6ff92396c6c97f4185e287f04eb19784 + if (!serverLevel.hasChunkAt(mutableBlockPos)) continue; + if (isWithinRestriction(restrictRadius, restrictCenter, mutableBlockPos) + && isValidTargetAsync(ty, toRemove, mob, serverLevel, mutableBlockPos)) { -+ pendingBlock.setRelease(mutableBlockPos.immutable()); ++ ctx.result = mutableBlockPos.immutable(); + return true; + } + } @@ -1146,7 +1236,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..6ff92396c6c97f4185e287f04eb19784 + } } diff --git a/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java b/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java -index 3c274d917bca9de87abfb842f5f332e112a7a2d7..f001bd12a2ec5696b8ee628484597de09b2eed32 100644 +index 3c274d917bca9de87abfb842f5f332e112a7a2d7..aa743d8f4ec0c1d78e5e58a7b5e36bca08b8b2ef 100644 --- a/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java +++ b/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java @@ -19,10 +19,20 @@ public class OfferFlowerGoal extends Goal { @@ -1162,29 +1252,23 @@ index 3c274d917bca9de87abfb842f5f332e112a7a2d7..f001bd12a2ec5696b8ee628484597de0 + return true; + } + if (this.golem.getRandom().nextInt(8000) != 0) { - return false; ++ return false; + } + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + getVillagerAsync(); -+ return false; + return false; + // Leaf end - Async offer flower finding } else { this.villager = getServerLevel(this.golem) .getNearestEntity( -@@ -38,6 +48,65 @@ public class OfferFlowerGoal extends Goal { +@@ -38,6 +48,47 @@ public class OfferFlowerGoal extends Goal { } } + + // Leaf start - Async look finding -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; + protected boolean poll() { -+ Villager t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return false; -+ } ++ if (!(this.golem.getGoalCtx().result() instanceof Villager t)) { return false; } + var serverLevel = getServerLevel(this.golem); + if (!t.isAlive() || !OFFER_TARGER_CONTEXT.test(serverLevel, this.golem, t)) { + return false; @@ -1194,29 +1278,20 @@ index 3c274d917bca9de87abfb842f5f332e112a7a2d7..f001bd12a2ec5696b8ee628484597de0 + } + + protected void getVillagerAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final var golem = this.golem; -+ -+ if (golem == null || golem.isRemoved() || !golem.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ ++ final var ctx = golem.getGoalCtx(); ++ if (!ctx.state) return; + final double x = golem.getX(); + final double y = golem.getEyeY(); + final double z = golem.getZ(); + final var serverLevel = getServerLevel(golem); + final var bound = golem.getBoundingBox().inflate(6.0, 2.0, 6.0); -+ final var pendingTarget = this.pendingTarget; -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (golem == null || !golem.isAlive() || golem.level() != serverLevel) { + return; + } -+ var result = serverLevel.getNearestEntity( ++ ctx.result = serverLevel.getNearestEntity( + serverLevel.getEntitiesOfClass(Villager.class, bound, livingEntity -> true), + OFFER_TARGER_CONTEXT, + golem, @@ -1224,13 +1299,10 @@ index 3c274d917bca9de87abfb842f5f332e112a7a2d7..f001bd12a2ec5696b8ee628484597de0 + y, + z + ); -+ pendingTarget.setRelease(result); + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async look finding + @@ -1286,28 +1358,18 @@ index 95fa516910a3834bbd4db6d11279e13a1f0dac41..f7ddae601abbe9e22a35c7cb4f9763e6 + // Leaf end - Async search block } diff --git a/net/minecraft/world/entity/ai/goal/TemptGoal.java b/net/minecraft/world/entity/ai/goal/TemptGoal.java -index f88f618d34fb343b31de3af1a875d6633703df71..7c6d90ed1878b29904ceaca1089eaadd05a0d570 100644 +index f88f618d34fb343b31de3af1a875d6633703df71..37aa00347e9cf391e447cff775e4493b36e05beb 100644 --- a/net/minecraft/world/entity/ai/goal/TemptGoal.java +++ b/net/minecraft/world/entity/ai/goal/TemptGoal.java -@@ -36,14 +36,84 @@ public class TemptGoal extends Goal { +@@ -36,12 +36,62 @@ public class TemptGoal extends Goal { this.targetingConditions = TEMPT_TARGETING.copy().selector((entity, level) -> this.shouldFollow(entity)); } + // Leaf start - Async Tempt Finding -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ + private boolean poll() { -+ LivingEntity t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ t = this.player; -+ } -+ if (t == null) { -+ return false; -+ } ++ if (!(this.mob.getGoalCtx().result() instanceof Player t)) { return false; } + var serverLevel = getServerLevel(this.mob); -+ if (serverLevel == null || !t.isAlive() || !this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)).test(serverLevel, this.mob, t)) { ++ if (!t.isAlive() || !this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)).test(serverLevel, this.mob, t)) { + return false; + } + this.player = t; @@ -1315,34 +1377,23 @@ index f88f618d34fb343b31de3af1a875d6633703df71..7c6d90ed1878b29904ceaca1089eaadd + } + + private void getNearestPlayerAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final var mob = this.mob; -+ -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ ++ final var ctx = mob.getGoalCtx(); ++ if (!ctx.state) return; + final var serverLevel = getServerLevel(mob); -+ final var pendingTarget = this.pendingTarget; + final var conditions = this.targetingConditions + .range(mob.getAttributeValue(Attributes.TEMPT_RANGE)) + .copy(); -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob == null || mob.isRemoved() || !mob.isAlive() || mob.level() != serverLevel) { + return; + } -+ pendingTarget.setRelease(serverLevel.getNearestPlayer(conditions, mob)); ++ ctx.result = serverLevel.getNearestPlayer(conditions, mob); + } catch (Exception e) { -+ LOGGER.error("Exception getting player", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting player", e); + } -+ }); ++ }; + } + // Leaf end - Async Tempt Finding @Override @@ -1351,10 +1402,8 @@ index f88f618d34fb343b31de3af1a875d6633703df71..7c6d90ed1878b29904ceaca1089eaadd this.calmDown--; return false; } else { -- this.player = getServerLevel(this.mob) -- .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); + // Leaf start - Async Tempt Finding -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchPlayerTempt) { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + if (poll()) { + if (this.player != null) { + org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT); @@ -1363,21 +1412,20 @@ index f88f618d34fb343b31de3af1a875d6633703df71..7c6d90ed1878b29904ceaca1089eaadd + } + this.player = (event.getTarget() == null) ? null : ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle(); + } -+ return this.player != null; ++ if (this.player != null) { ++ return true; ++ } + } else { + getNearestPlayerAsync(); + return false; + } -+ } else { -+ this.player = getServerLevel(this.mob) -+ .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); + } + // Leaf end - Async Tempt Finding + this.player = getServerLevel(this.mob) + .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); // CraftBukkit start - if (this.player != null) { - org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT); diff --git a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java -index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..04bc007a138d5f5875fa89f52d573693fa96c656 100644 +index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..921418da5f026edad4c78ba51127bb758c34bb9a 100644 --- a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java @@ -16,7 +16,7 @@ public class DefendVillageTargetGoal extends TargetGoal { @@ -1389,19 +1437,13 @@ index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..04bc007a138d5f5875fa89f52d573693 public DefendVillageTargetGoal(IronGolem golem) { super(golem, false, true); -@@ -24,8 +24,82 @@ public class DefendVillageTargetGoal extends TargetGoal { +@@ -24,8 +24,63 @@ public class DefendVillageTargetGoal extends TargetGoal { this.setFlags(EnumSet.of(Goal.Flag.TARGET)); } + // Leaf start - Async Target Finding -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); + protected boolean poll() { -+ LivingEntity t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return false; -+ } ++ if (!(this.mob.getGoalCtx().result() instanceof LivingEntity t)) { return false; } + ServerLevel serverLevel = getServerLevel(this.mob); + if (serverLevel == null || !t.isAlive() || !attackTargeting.test(serverLevel, golem, t)) { + return false; @@ -1415,24 +1457,12 @@ index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..04bc007a138d5f5875fa89f52d573693 + } + + private void findTargetAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final IronGolem mob = this.golem; -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ -+ final double x = mob.getX(); -+ final double y = mob.getEyeY(); -+ final double z = mob.getZ(); ++ final var ctx = mob.getGoalCtx(); ++ if (!ctx.state) return; + AABB bound = this.golem.getBoundingBox().inflate(10.0, 8.0, 10.0); + final ServerLevel serverLevel = getServerLevel(mob); -+ final var pendingTarget = this.pendingTarget; -+ -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob.level() != serverLevel || !mob.isAlive()) { + return; @@ -1445,16 +1475,15 @@ index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..04bc007a138d5f5875fa89f52d573693 + for (Player player : nearbyPlayers) { + int playerReputation = villager.getPlayerReputation(player); + if (playerReputation <= -100) { -+ pendingTarget.setRelease(player); ++ ctx.result = player; ++ break; + } + } + } + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async Target Finding + @@ -1472,7 +1501,7 @@ index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..04bc007a138d5f5875fa89f52d573693 AABB aabb = this.golem.getBoundingBox().inflate(10.0, 8.0, 10.0); ServerLevel serverLevel = getServerLevel(this.golem); List nearbyEntities = serverLevel.getNearbyEntities(Villager.class, this.attackTargeting, this.golem, aabb); -@@ -42,6 +116,7 @@ public class DefendVillageTargetGoal extends TargetGoal { +@@ -42,6 +97,7 @@ public class DefendVillageTargetGoal extends TargetGoal { } } @@ -1481,10 +1510,10 @@ index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..04bc007a138d5f5875fa89f52d573693 && (!(this.potentialTarget instanceof Player) || !this.potentialTarget.isSpectator() && !((Player)this.potentialTarget).isCreative()); } diff --git a/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java -index 25fe78116ce01eeefe5c958423734195d27302eb..50228ccc92181d021b2f1cf164671ce4b2095c75 100644 +index 25fe78116ce01eeefe5c958423734195d27302eb..e19f2cb98c9669deb3a19114264668fbb29d3cc5 100644 --- a/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java -@@ -73,6 +73,59 @@ public class HurtByTargetGoal extends TargetGoal { +@@ -73,6 +73,56 @@ public class HurtByTargetGoal extends TargetGoal { protected void alertOthers() { double followDistance = this.getFollowDistance(); AABB aabb = AABB.unitCubeFromLowerCorner(this.mob.position()).inflate(followDistance, 10.0, followDistance); @@ -1492,12 +1521,13 @@ index 25fe78116ce01eeefe5c958423734195d27302eb..50228ccc92181d021b2f1cf164671ce4 + // Leaf start - async alert other + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.alertOther) { + final var self = this.mob; ++ final var ctx = self.getGoalCtx(); ++ if (!ctx.state) return; + final var serverLevel = getServerLevel(self); -+ final var alertTarget = this.mob.getLastHurtByMob(); + final var toIgnoreAlert = this.toIgnoreAlert; -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { -+ if (alertTarget == null || self == null || self.level() != serverLevel) { ++ if (self == null || self.level() != serverLevel) { + return; + } + var toAlert = new java.util.ArrayList(); @@ -1528,15 +1558,11 @@ index 25fe78116ce01eeefe5c958423734195d27302eb..50228ccc92181d021b2f1cf164671ce4 + toAlert.add(mob); + } + } -+ serverLevel.getServer().execute(() -> { -+ for (var livingEntity : toAlert) { -+ alertOther(livingEntity, alertTarget); -+ } -+ }); ++ ctx.result = toAlert; + } catch (Exception e) { + org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception alertOthers", e); + } -+ }); ++ }; + return; + } + // Leaf end - async alert other @@ -1544,7 +1570,7 @@ index 25fe78116ce01eeefe5c958423734195d27302eb..50228ccc92181d021b2f1cf164671ce4 List entitiesOfClass = this.mob .level() .getEntitiesOfClass((Class)this.mob.getClass(), aabb, EntitySelector.NO_SPECTATORS); -@@ -86,6 +139,7 @@ public class HurtByTargetGoal extends TargetGoal { +@@ -86,6 +136,7 @@ public class HurtByTargetGoal extends TargetGoal { } mob = (Mob)var5.next(); @@ -1552,7 +1578,7 @@ index 25fe78116ce01eeefe5c958423734195d27302eb..50228ccc92181d021b2f1cf164671ce4 if (this.mob != mob && mob.getTarget() == null && (!(this.mob instanceof TamableAnimal) || ((TamableAnimal)this.mob).getOwner() == ((TamableAnimal)mob).getOwner()) -@@ -96,6 +150,7 @@ public class HurtByTargetGoal extends TargetGoal { +@@ -96,6 +147,7 @@ public class HurtByTargetGoal extends TargetGoal { boolean flag = false; @@ -1560,23 +1586,33 @@ index 25fe78116ce01eeefe5c958423734195d27302eb..50228ccc92181d021b2f1cf164671ce4 for (Class clazz : this.toIgnoreAlert) { if (mob.getClass() == clazz) { flag = true; +@@ -113,6 +165,15 @@ public class HurtByTargetGoal extends TargetGoal { + } + } + ++ // Leaf start - async alert other ++ public void poll() { ++ if (!(this.mob.getGoalCtx().result() instanceof List toAlert)) { return; } ++ for (var livingEntity : toAlert) { ++ alertOther((Mob) livingEntity, this.mob.getLastHurtByMob()); ++ } ++ } ++ // Leaf end - async alert other ++ + protected void alertOther(Mob mob, LivingEntity target) { + mob.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY, true); // CraftBukkit - reason + } diff --git a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -index 41ee3cdc45ecc8376a2203ed588bb544ed377294..24c7567ffe88d4767371ac0082aa26cf5d2c2ed8 100644 +index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f07d0db481aecb8260c09eafef2c1cd582a8502c 100644 --- a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -@@ -41,8 +41,73 @@ public class NearestAttackableTargetGoal extends TargetG +@@ -41,8 +41,53 @@ public class NearestAttackableTargetGoal extends TargetG this.targetConditions = TargetingConditions.forCombat().range(this.getFollowDistance()).selector(selector); } + // Leaf start - Async Target Finding -+ private static final org.apache.logging.log4j.Logger LOGGER = org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER; -+ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); -+ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); + protected boolean poll() { -+ LivingEntity t = pendingTarget.getAndSet(null); -+ if (t == null) { -+ return false; -+ } ++ if (!(this.mob.getGoalCtx().result() instanceof LivingEntity t)) { return false; } + ServerLevel serverLevel = getServerLevel(this.mob); + if (serverLevel == null || !t.isAlive() || !this.getTargetConditions().test(serverLevel, this.mob, t)) { + return false; @@ -1586,16 +1622,9 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..24c7567ffe88d4767371ac0082aa26cf + } + + private void findTargetAsync() { -+ if (!isSearching.compareAndSet(false, true)) { -+ return; -+ } -+ + final Mob mob = this.mob; -+ if (mob == null || mob.isRemoved() || !mob.isAlive()) { -+ isSearching.setRelease(false); -+ return; -+ } -+ ++ final var ctx = mob.getGoalCtx(); ++ if (!ctx.state) return; + final double x = mob.getX(); + final double y = mob.getEyeY(); + final double z = mob.getZ(); @@ -1603,28 +1632,21 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..24c7567ffe88d4767371ac0082aa26cf + final Class targetType = this.targetType; + final AABB targetSearch = getTargetSearchArea(this.getFollowDistance()); + final ServerLevel serverLevel = getServerLevel(mob); -+ final var pendingTarget = this.pendingTarget; -+ -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob.level() != serverLevel || mob.isRemoved() || !mob.isAlive()) { + return; + } + + if (targetType != Player.class && targetType != ServerPlayer.class) { -+ final java.util.List entities = mob.level().getEntitiesOfClass(targetType, targetSearch, entity -> entity != null && entity != mob && entity.isAlive()); -+ var result = serverLevel.getNearestEntity(entities, targetConditions, mob, x,y,z); -+ pendingTarget.setRelease(result); ++ ctx.result = serverLevel.getNearestEntity(mob.level().getEntitiesOfClass(targetType, targetSearch, entity -> entity != null && entity != mob && entity.isAlive()), targetConditions, mob, x,y,z); + } else { -+ var result = serverLevel.getNearestPlayer(targetConditions, mob, x, y, z); -+ pendingTarget.setRelease(result); ++ ctx.result = serverLevel.getNearestPlayer(targetConditions, mob, x, y, z); + } + } catch (Exception e) { -+ LOGGER.error("Exception getting entities", e); -+ } finally { -+ isSearching.setRelease(false); ++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } -+ }); ++ }; + } + // Leaf end - Async Target Finding + @@ -1638,19 +1660,13 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..24c7567ffe88d4767371ac0082aa26cf if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) { return false; } else { -@@ -57,6 +122,21 @@ public class NearestAttackableTargetGoal extends TargetG +@@ -57,6 +102,15 @@ public class NearestAttackableTargetGoal extends TargetG protected void findTarget() { ServerLevel serverLevel = getServerLevel(this.mob); + + // Leaf start - Async Target Finding -+ if (targetType == Player.class) { -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchPlayer) { -+ this.findTargetAsync(); -+ this.target = null; -+ return; -+ } -+ } else if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + this.findTargetAsync(); + this.target = null; + return; @@ -1660,7 +1676,7 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..24c7567ffe88d4767371ac0082aa26cf if (this.targetType != Player.class && this.targetType != ServerPlayer.class) { this.target = serverLevel.getNearestEntity( this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true), -@@ -81,7 +161,7 @@ public class NearestAttackableTargetGoal extends TargetG +@@ -81,7 +135,7 @@ public class NearestAttackableTargetGoal extends TargetG this.target = target; } @@ -1712,20 +1728,22 @@ index abf57494950f55bbd75f335f26736cb9e703c197..683722fbd07fcae9cdd72928ed25fd08 } } diff --git a/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java -index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..8db8dc0b9a79bc56568e22a8e99354de599ed23b 100644 +index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..93da18d921a1cf7d72f441d9415435ff9c417f1f 100644 --- a/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java -@@ -37,6 +37,36 @@ public class ResetUniversalAngerTargetGoal extends G +@@ -37,6 +37,34 @@ public class ResetUniversalAngerTargetGoal extends G this.lastHurtByPlayerTimestamp = this.mob.getLastHurtByMobTimestamp(); this.mob.forgetCurrentTargetAndRefreshUniversalAnger(); if (this.alertOthersOfSameType) { + // Leaf start - async alert other + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.alertOther) { + final var mob = this.mob; ++ final var ctx = mob.getGoalCtx(); ++ if (!ctx.state) return; + final var serverLevel = getServerLevel(mob); + final double followRange = this.mob.getAttributeValue(Attributes.FOLLOW_RANGE); + final AABB aabb = AABB.unitCubeFromLowerCorner(this.mob.position()).inflate(followRange, 10.0, followRange); -+ serverLevel.asyncAITasks.add(() -> { ++ ctx.wake = () -> { + try { + if (mob == null || serverLevel != mob.level() || !mob.isAlive()) { + return; @@ -1737,24 +1755,30 @@ index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..8db8dc0b9a79bc56568e22a8e99354de + toStop.add((NeutralMob) entity); + } + } -+ serverLevel.getServer().execute(() -> { -+ for (NeutralMob neutralMob : toStop) { -+ neutralMob.forgetCurrentTargetAndRefreshUniversalAnger(); -+ } -+ }); ++ ctx.result = toStop; + } catch (Exception e) { + org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception alertOthers forgetCurrentTargetAndRefreshUniversalAnger", e); + } -+ }); ++ }; + return; + } + // Leaf end - async alert other this.getNearbyMobsOfSameType() .stream() .filter(mob -> mob != this.mob) -@@ -48,6 +78,7 @@ public class ResetUniversalAngerTargetGoal extends G +@@ -47,7 +75,17 @@ public class ResetUniversalAngerTargetGoal extends G + super.start(); } ++ // Leaf start - async alert other ++ public void poll() { ++ if (!(this.mob.getGoalCtx().result() instanceof List toStop)) { return; } ++ for (var neutralMob : toStop) { ++ ((NeutralMob)neutralMob).forgetCurrentTargetAndRefreshUniversalAnger(); ++ } ++ } ++ // Leaf end - async alert other ++ private List getNearbyMobsOfSameType() { + // Leaf - async alert other double attributeValue = this.mob.getAttributeValue(Attributes.FOLLOW_RANGE); @@ -1854,10 +1878,10 @@ index 90452f0945e761077608692877677f522d38bccd..9e380f5b4dcedb115f8893dd382f28ea class FoxFloatGoal extends FloatGoal { diff --git a/net/minecraft/world/entity/animal/Panda.java b/net/minecraft/world/entity/animal/Panda.java -index 0a6a3060f3690ab2d8439d66e6fd6f0c5dc20688..050b060c711ea33d413fba506653618d8c4c4bd4 100644 +index 0a6a3060f3690ab2d8439d66e6fd6f0c5dc20688..d3466ebc9d0a57d0dcefa98d65fe42d8c827e6d3 100644 --- a/net/minecraft/world/entity/animal/Panda.java +++ b/net/minecraft/world/entity/animal/Panda.java -@@ -991,31 +991,44 @@ public class Panda extends Animal { +@@ -991,31 +991,39 @@ public class Panda extends Animal { @Override public boolean canUse() { @@ -1887,12 +1911,7 @@ index 0a6a3060f3690ab2d8439d66e6fd6f0c5dc20688..050b060c711ea33d413fba506653618d - ); - } + } -+ if (this.lookAtType == Player.class) { -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchPlayer) { -+ getLookAsync(); -+ return false; -+ } -+ } else if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + getLookAsync(); + return false; + } @@ -2065,3 +2084,31 @@ index ae4ee948971e931e4fdc4ec2187f5182195c626c..7b878761eefc41fa735ef67c4a6dcbe9 } static class StriderPathNavigation extends GroundPathNavigation { +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index ac7729d1caa80155697bfe0e8646e4eda5d1780e..d2851eb4c3e7eb2a1976075e2df0edb33b1c411d 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -1548,6 +1548,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + this.moonrise$midTickTasks(); + } + // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) - do not bother with condition work / make configurable ++ ((ServerLevel) this).leafMidTickTasks(); // Leaf - Async target finding + // Paper end - rewrite chunk system + } + } +@@ -1817,9 +1818,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + @Override + public List getEntities(@Nullable Entity entity, AABB boundingBox, Predicate predicate) { +- if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) +- ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) +- List list = Lists.newArrayList(); ++ // Leaf start - Async target finding ++ // if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) ++ // ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ // Leaf end - Async target finding ++ ++ // List list = Lists.newArrayList(); // Leaf - unused + + // Paper start - rewrite chunk system + final List ret = new java.util.ArrayList<>(); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java index d27be899..a14c27e8 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java @@ -1,19 +1,76 @@ package org.dreeam.leaf.async.ai; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.Mob; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dreeam.leaf.config.modules.async.AsyncTargetFinding; +import org.dreeam.leaf.util.queue.SpscIntQueue; -import java.util.List; -import java.util.concurrent.ExecutorService; +import java.util.concurrent.locks.LockSupport; public class AsyncGoalExecutor { - public static ExecutorService EXECUTOR; + public static final Logger LOGGER = LogManager.getLogger("Leaf Async Goal"); + final SpscIntQueue queue; + final SpscIntQueue wake; + private final AsyncGoalThread thread; + private final ServerLevel serverLevel; + private boolean dirty = false; + private long tickCount = 0L; - public static final Logger LOGGER = LogManager.getLogger("Leaf Async Entity Lookup"); + public AsyncGoalExecutor(AsyncGoalThread thread, ServerLevel serverLevel) { + this.serverLevel = serverLevel; + queue = new SpscIntQueue(AsyncTargetFinding.queueSize); + wake = new SpscIntQueue(AsyncTargetFinding.queueSize); + this.thread = thread; + } - public static void runTasks(List tasks) { - for (Runnable task : tasks) { - task.run(); + boolean wake(int id) { + Entity entity = this.serverLevel.getEntities().get(id); + if (entity == null || entity.isRemoved() || !(entity instanceof Mob m)) { + return false; } + m.goalSelector.wake(); + m.targetSelector.wake(); + return true; + } + + public final void submit(int entityId) { + if (!this.queue.send(entityId)) { + LockSupport.unpark(thread); + while (!this.queue.send(entityId)) { + Thread.onSpinWait(); + } + } + dirty = true; + } + + public final void unpark() { + if (dirty) LockSupport.unpark(thread); + dirty = false; + } + + public final void midTick() { + while (true) { + int id = this.wake.recv(); + if (id == Integer.MAX_VALUE) { + break; + } + Entity entity = this.serverLevel.getEntities().get(id); + if (entity == null || !entity.isAlive() || !(entity instanceof Mob mob)) { + continue; + } + mob.tickingTarget = true; + boolean a = mob.targetSelector.poll(); + mob.tickingTarget = false; + boolean b = mob.goalSelector.poll(); + if (a || b) { + submit(id); + } + } + if ((tickCount & 3L) == 0L) unpark(); + tickCount += 1; } } + diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java new file mode 100644 index 00000000..9d8abe8c --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java @@ -0,0 +1,37 @@ +package org.dreeam.leaf.async.ai; + +import net.minecraft.Util; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; + +import java.util.concurrent.locks.LockSupport; + +public class AsyncGoalThread extends Thread { + public AsyncGoalThread(final MinecraftServer server) { + super(() -> run(server), "Leaf Async Goal Thread"); + this.setDaemon(true); + this.setUncaughtExceptionHandler(Util::onThreadException); + this.setPriority(Thread.NORM_PRIORITY - 1); + this.start(); + } + + private static void run(MinecraftServer server) { + while (server.isRunning()) { + LockSupport.park(); + for (ServerLevel level : server.getAllLevels()) { + var exec = level.asyncGoalExecutor; + while (true) { + int id = exec.queue.recv(); + if (id == Integer.MAX_VALUE) { + break; + } + if (exec.wake(id)) { + while (!exec.wake.send(id)) { + Thread.onSpinWait(); + } + } + } + } + } + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java new file mode 100644 index 00000000..82560379 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java @@ -0,0 +1,17 @@ +package org.dreeam.leaf.async.ai; + +import org.jetbrains.annotations.Nullable; + +public class Waker { + @Nullable + public volatile Runnable wake = null; + @Nullable + public volatile Object result = null; + public volatile boolean state = true; + + public final @Nullable Object result() { + Object result = this.result; + this.result = null; + return result; + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java index dc6442c4..ea66a4d5 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java @@ -1,16 +1,10 @@ package org.dreeam.leaf.config.modules.async; -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import org.dreeam.leaf.async.ai.AsyncGoalExecutor; import org.dreeam.leaf.config.ConfigModules; import org.dreeam.leaf.config.EnumConfigCategory; import org.dreeam.leaf.config.annotations.Experimental; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - public class AsyncTargetFinding extends ConfigModules { public String getBasePath() { @@ -20,18 +14,17 @@ public class AsyncTargetFinding extends ConfigModules { @Experimental public static boolean enabled = false; public static boolean alertOther = true; - public static boolean searchBlock = false; + public static boolean searchBlock = true; public static boolean searchEntity = true; - public static boolean searchPlayer = false; - public static boolean searchPlayerTempt = false; + public static int queueSize = 4096; private static boolean asyncTargetFindingInitialized; @Override public void onLoaded() { config.addCommentRegionBased(getBasePath(), """ **Experimental feature** - This moves the expensive entity target search calculations to a background thread while - keeping the actual entity validation on the main thread.""", + This moves the expensive entity and block search calculations to background thread while + keeping the actual validation on the main thread.""", """ **实验性功能** 这会将昂贵的实体目标搜索计算移至后台线程, 同时在主线程上保持实际的实体验证."""); @@ -44,29 +37,16 @@ public class AsyncTargetFinding extends ConfigModules { enabled = config.getBoolean(getBasePath() + ".enabled", enabled); alertOther = config.getBoolean(getBasePath() + ".async-alert-other", true); - searchBlock = config.getBoolean(getBasePath() + ".async-search-block", false); + searchBlock = config.getBoolean(getBasePath() + ".async-search-block", true); searchEntity = config.getBoolean(getBasePath() + ".async-search-entity", true); - searchPlayer = config.getBoolean(getBasePath() + ".async-search-player", false); - searchPlayerTempt = config.getBoolean(getBasePath() + ".async-search-player-tempt", false); + queueSize = config.getInt(getBasePath() + ".queue-size", 100_000); + if (queueSize <= 0) { + queueSize = 4096; + } if (!enabled) { alertOther = false; searchEntity = false; searchBlock = false; - searchPlayer = false; - searchPlayerTempt = false; - return; } - AsyncGoalExecutor.EXECUTOR = new ThreadPoolExecutor( - 1, - 1, - 0L, - TimeUnit.MILLISECONDS, - new ArrayBlockingQueue<>(128), - new ThreadFactoryBuilder() - .setNameFormat("Leaf Async Target Finding Thread") - .setDaemon(true) - .setPriority(Thread.NORM_PRIORITY - 2) - .build(), - new ThreadPoolExecutor.CallerRunsPolicy()); } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/queue/SpscIntQueue.java b/leaf-server/src/main/java/org/dreeam/leaf/util/queue/SpscIntQueue.java new file mode 100644 index 00000000..9afc72df --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/queue/SpscIntQueue.java @@ -0,0 +1,63 @@ +package org.dreeam.leaf.util.queue; + +/// Lock-free Single Producer Single Consumer Queue +public class SpscIntQueue { + private final int[] data; + private final PaddedAtomicInteger producerIdx = new PaddedAtomicInteger(); + private final PaddedAtomicInteger producerCachedIdx = new PaddedAtomicInteger(); + private final PaddedAtomicInteger consumerIdx = new PaddedAtomicInteger(); + private final PaddedAtomicInteger consumerCachedIdx = new PaddedAtomicInteger(); + + public SpscIntQueue(int size) { + this.data = new int[size + 1]; + } + + public final boolean send(int e) { + final int idx = producerIdx.getOpaque(); + int nextIdx = idx + 1; + if (nextIdx == data.length) { + nextIdx = 0; + } + int cachedIdx = consumerCachedIdx.getPlain(); + if (nextIdx == cachedIdx) { + cachedIdx = consumerIdx.getAcquire(); + consumerCachedIdx.setPlain(cachedIdx); + if (nextIdx == cachedIdx) { + return false; + } + } + data[idx] = e; + producerIdx.setRelease(nextIdx); + return true; + } + + + public final int recv() { + final int idx = consumerIdx.getOpaque(); + int cachedIdx = producerCachedIdx.getPlain(); + if (idx == cachedIdx) { + cachedIdx = producerIdx.getAcquire(); + producerCachedIdx.setPlain(cachedIdx); + if (idx == cachedIdx) { + return Integer.MAX_VALUE; + } + } + int e = data[idx]; + int nextIdx = idx + 1; + if (nextIdx == data.length) { + nextIdx = 0; + } + consumerIdx.setRelease(nextIdx); + return e; + } + + public final int size() { + return this.data.length; + } + + static class PaddedAtomicInteger extends java.util.concurrent.atomic.AtomicInteger { + @SuppressWarnings("unused") + private int i1, i2, i3, i4, i5, i6, i7, i8, + i9, i10, i11, i12, i13, i14, i15; + } +} From 11ce54da4c9aecc83b4a61c9601ecb139bfc3685 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Thu, 1 May 2025 22:32:18 +0800 Subject: [PATCH 33/45] default queueSize to 4096 --- .../dreeam/leaf/config/modules/async/AsyncTargetFinding.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java index ea66a4d5..bd26abb4 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java @@ -39,7 +39,7 @@ public class AsyncTargetFinding extends ConfigModules { alertOther = config.getBoolean(getBasePath() + ".async-alert-other", true); searchBlock = config.getBoolean(getBasePath() + ".async-search-block", true); searchEntity = config.getBoolean(getBasePath() + ".async-search-entity", true); - queueSize = config.getInt(getBasePath() + ".queue-size", 100_000); + queueSize = config.getInt(getBasePath() + ".queue-size", 4096); if (queueSize <= 0) { queueSize = 4096; } From 8fe6d13765676050ac6deb9a8552558ee228866d Mon Sep 17 00:00:00 2001 From: sh1kcs <113757412+sh2kcs@users.noreply.github.com> Date: Thu, 1 May 2025 20:49:50 +0600 Subject: [PATCH 34/45] [ci skip] Update docs links (#305) * Update README.md * Update README.md * Update README.md * Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b2f4b456..3cfa440f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Github Releases](https://img.shields.io/badge/Download-Releases-blue?&style=for-the-badge&colorA=19201a&colorB=298046)](https://github.com/Winds-Studio/Leaf/releases)⠀ [![Github Actions Build](https://img.shields.io/github/actions/workflow/status/Winds-Studio/Leaf/build-1214.yml?&style=for-the-badge&colorA=19201a&colorB=298046)](https://github.com/Winds-Studio/Leaf/actions)⠀ [![Discord](https://img.shields.io/discord/1145991395388162119?label=discord&style=for-the-badge&colorA=19201a&colorB=298046)](https://discord.gg/gfgAwdSEuM) -[![Docs](https://img.shields.io/badge/Docs-docs.leafmc.one-blue?label=docs&style=for-the-badge&colorA=19201a&colorB=298046)](https://docs.leafmc.one) +[![Docs](https://img.shields.io/badge/Docs-leafmc.one/docs/-blue?label=docs&style=for-the-badge&colorA=19201a&colorB=298046)](https://www.leafmc.one/docs/) **Leaf** is a [Paper](https://papermc.io/) fork designed to customized and high-performance, built on top of [Gale](https://github.com/Dreeam-qwq/Gale) with optimizations and fixes from other forks. @@ -43,7 +43,7 @@ You can find the latest successful build in [GitHub Action](https://github.com/W **Please note Java >= 21 is required.** ## 📄 Documentation -Documentation on how to use/configure Leaf: [docs.leafmc.one](https://docs.leafmc.one) +Documentation on how to use/configure Leaf: [leafmc.one/docs/](https://www.leafmc.one/docs/) ## 📦 Building Building a Paperclip JAR for distribution: From 4586961036535289aae4cb25718badeb5d461453 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Thu, 1 May 2025 15:05:05 -0400 Subject: [PATCH 35/45] Back port some Paper patches * PaperPR: Add ticket on player join to avoid chunk * PaperPR: Fix save/load NaN Entity Motion * PaperPR: Fix unnecessary map data saves --- ...et-on-player-join-to-avoid-chunk-loa.patch | 46 +++++++++++++++++ ...erPR-Fix-save-load-NaN-Entity-Motion.patch | 38 ++++++++++++++ ...perPR-Fix-unnecessary-map-data-saves.patch | 45 ++++++++++++++++ ...erPR-Fix-save-load-NaN-Entity-Motion.patch | 36 +++++++++++++ ...perPR-Fix-unnecessary-map-data-saves.patch | 51 +++++++++++++++++++ 5 files changed, 216 insertions(+) create mode 100644 leaf-server/minecraft-patches/features/0159-PaperPR-Add-ticket-on-player-join-to-avoid-chunk-loa.patch create mode 100644 leaf-server/minecraft-patches/features/0160-PaperPR-Fix-save-load-NaN-Entity-Motion.patch create mode 100644 leaf-server/minecraft-patches/features/0161-PaperPR-Fix-unnecessary-map-data-saves.patch create mode 100644 leaf-server/paper-patches/features/0035-PaperPR-Fix-save-load-NaN-Entity-Motion.patch create mode 100644 leaf-server/paper-patches/features/0036-PaperPR-Fix-unnecessary-map-data-saves.patch diff --git a/leaf-server/minecraft-patches/features/0159-PaperPR-Add-ticket-on-player-join-to-avoid-chunk-loa.patch b/leaf-server/minecraft-patches/features/0159-PaperPR-Add-ticket-on-player-join-to-avoid-chunk-loa.patch new file mode 100644 index 00000000..077569ed --- /dev/null +++ b/leaf-server/minecraft-patches/features/0159-PaperPR-Add-ticket-on-player-join-to-avoid-chunk-loa.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Fri, 13 Sep 2024 14:32:32 -0700 +Subject: [PATCH] PaperPR: Add ticket on player join to avoid chunk + load-unload-load cycle + +Original license: GPLv3 +Original project: https://github.com/PaperMC/Paper +Paper pull request: https://github.com/PaperMC/Paper/pull/11398 + +Adding the entity will add and then immediately remove an entity load ticket, which would result in the chunk loading and then unloading before being loaded again once the player chunk loader reacts (delay can vary based on rate limit configs) +By adding a ticket with a short removal delay we attempt to keep the chunk loaded until the player chunk loader reacts, but this is not a guarantee due to the aforementioned rate limit configs. Plugins should still handle load/unload events as normal, however this will reduce redundant calls. +The delay is currently set to 2 seconds, however, we may want to adjust this before merging (for example the player chunk unload delay is 5 seconds) + +fixes Paper#9581 + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +index c0f14d2bc30a186511bafddc2e80f29b686887c5..d8040bb5a07546b97f66a4db1f8f25a63c613e17 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +@@ -47,6 +47,7 @@ public final class RegionizedPlayerChunkLoader { + + public static final TicketType PLAYER_TICKET = TicketType.create("chunk_system:player_ticket", Long::compareTo); + public static final TicketType PLAYER_TICKET_DELAYED = TicketType.create("chunk_system:player_ticket_delayed", Long::compareTo, 5 * 20); ++ public static final TicketType PLAYER_JOIN = TicketType.create("chunk_system:player_join", (a, b) -> 0, 5 * 20); // Paper - Add ticket on player join to avoid chunk load-unload-load cycle + + public static final int MIN_VIEW_DISTANCE = 2; + public static final int MAX_VIEW_DISTANCE = 32; +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index 0b8b4658dbbad1bacc13e97b4fc0cdcea7e36a06..eba382a63e9ed13c4ccbc05c6f2132e03a6b3007 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -432,6 +432,13 @@ public abstract class PlayerList { + // this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player))); // CraftBukkit - replaced with loop below + // Paper start - Fire PlayerJoinEvent when Player is actually ready; correctly register player BEFORE PlayerJoinEvent, so the entity is valid and doesn't require tick delay hacks + player.supressTrackerForLogin = true; ++ // Paper start - Add ticket on player join to avoid chunk load-unload-load cycle ++ serverLevel.moonrise$getChunkTaskScheduler().chunkHolderManager.addTicketAtLevel( ++ ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.PLAYER_JOIN, ++ player.chunkPosition(), ++ ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.TICK_TICKET_LEVEL, ++ net.minecraft.util.Unit.INSTANCE); ++ // Paper end - Add ticket on player join to avoid chunk load-unload-load cycle + serverLevel.addNewPlayer(player); + this.server.getCustomBossEvents().onPlayerConnect(player); // see commented out section below serverLevel.addPlayerJoin(player); + // Paper end - Fire PlayerJoinEvent when Player is actually ready diff --git a/leaf-server/minecraft-patches/features/0160-PaperPR-Fix-save-load-NaN-Entity-Motion.patch b/leaf-server/minecraft-patches/features/0160-PaperPR-Fix-save-load-NaN-Entity-Motion.patch new file mode 100644 index 00000000..a2db5aa6 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0160-PaperPR-Fix-save-load-NaN-Entity-Motion.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Doc +Date: Sat, 12 Apr 2025 12:41:08 -0400 +Subject: [PATCH] PaperPR: Fix save/load NaN Entity Motion + +Original license: GPLv3 +Original project: https://github.com/PaperMC/Paper +Paper pull request: https://github.com/PaperMC/Paper/pull/12269 + +Fix Paper#12262 using like the same logic than "pitch/yaw" for set to 0 when a value is NaN + +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index f114a8f5143799d72e36e0a535888c5fb25213e1..a81983182ee3e3b874ba83ddf9bbc6ea772a2997 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -2474,6 +2474,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + // CraftBukkit end + ++ this.setDeltaMovement(io.papermc.paper.util.MCUtil.sanitizeNanInf(this.deltaMovement, 0D)); // Paper - remove NaN values before usage in saving + Vec3 deltaMovement = this.getDeltaMovement(); + compound.put("Motion", this.newDoubleList(deltaMovement.x, deltaMovement.y, deltaMovement.z)); + // CraftBukkit start - Checking for NaN pitch/yaw and resetting to zero +@@ -2620,9 +2621,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + ListTag list = compound.getList("Pos", 6); + ListTag list1 = compound.getList("Motion", 6); + ListTag list2 = compound.getList("Rotation", 5); +- double _double = list1.getDouble(0); +- double _double1 = list1.getDouble(1); +- double _double2 = list1.getDouble(2); ++ // Paper start - avoid setting NaN values ++ double _double = list1.getDouble(0); _double = io.papermc.paper.util.MCUtil.sanitizeNanInf(_double, 0D); ++ double _double1 = list1.getDouble(1); _double1 = io.papermc.paper.util.MCUtil.sanitizeNanInf(_double1, 0D); ++ double _double2 = list1.getDouble(2); _double2 = io.papermc.paper.util.MCUtil.sanitizeNanInf(_double2, 0D); ++ // Paper end - avoid setting NaN values + this.setDeltaMovement( + Math.abs(_double) > 10.0 ? 0.0 : _double, Math.abs(_double1) > 10.0 ? 0.0 : _double1, Math.abs(_double2) > 10.0 ? 0.0 : _double2 + ); diff --git a/leaf-server/minecraft-patches/features/0161-PaperPR-Fix-unnecessary-map-data-saves.patch b/leaf-server/minecraft-patches/features/0161-PaperPR-Fix-unnecessary-map-data-saves.patch new file mode 100644 index 00000000..3ad86ace --- /dev/null +++ b/leaf-server/minecraft-patches/features/0161-PaperPR-Fix-unnecessary-map-data-saves.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dqu1J +Date: Tue, 29 Apr 2025 01:35:45 -0400 +Subject: [PATCH] PaperPR: Fix unnecessary map data saves + +Original license: GPLv3 +Original project: https://github.com/PaperMC/Paper +Paper pull request: https://github.com/PaperMC/Paper/pull/12296 + +Fixes Paper#12295 + +Currently, MapCanvas API unnecessarily causes map data file to save. + +The API calls this.mapView.worldMap.setColorsDirty() to mark colors dirty for players, however this has the side-effect of saving the map .dat files as well: +``` +public void setColorsDirty(int x, int z) { + this.setDirty(); // This saves the data file! + + for (MapItemSavedData.HoldingPlayer holdingPlayer : this.carriedBy) { + holdingPlayer.markColorsDirty(x, z); // This is what the API wants to do! + } +} +``` +This causes unnecessary lag during world saves, which scales with the amount of maps handled with API since last save. On servers that heavily rely on imageonmap-like plugins, it can cause lag spikes of dozens of seconds. + +This PR changes this method to add a boolean argument to the method that determines whether the file is saved or not, which is used by the API. An overload is added for compatibility, and for vanilla. + +diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +index 59829bb134555d96edcf4cbb844ccacb88c44961..76916ac06208348f718dffc9be232feb66b84d5c 100644 +--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -456,7 +456,12 @@ public class MapItemSavedData extends SavedData { + } + + public void setColorsDirty(int x, int z) { +- this.setDirty(); ++ // Paper start - Fix unnecessary map data saves ++ this.setColorsDirty(x, z, true); ++ } ++ public void setColorsDirty(int x, int z, boolean markFileDirty) { ++ if (markFileDirty) this.setDirty(); ++ // Paper end - Fix unnecessary map data saves + + for (MapItemSavedData.HoldingPlayer holdingPlayer : this.carriedBy) { + holdingPlayer.markColorsDirty(x, z); diff --git a/leaf-server/paper-patches/features/0035-PaperPR-Fix-save-load-NaN-Entity-Motion.patch b/leaf-server/paper-patches/features/0035-PaperPR-Fix-save-load-NaN-Entity-Motion.patch new file mode 100644 index 00000000..c01194bc --- /dev/null +++ b/leaf-server/paper-patches/features/0035-PaperPR-Fix-save-load-NaN-Entity-Motion.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Doc +Date: Sat, 12 Apr 2025 12:41:08 -0400 +Subject: [PATCH] PaperPR: Fix save/load NaN Entity Motion + +Original license: GPLv3 +Original project: https://github.com/PaperMC/Paper +Paper pull request: https://github.com/PaperMC/Paper/pull/12269 + +Fix Paper#12262 using like the same logic than "pitch/yaw" for set to 0 when a value is NaN + +diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java +index a4ac34ebb58a404f4fca7e763e61d4ab05ee3af4..ed2cee2bd3ab7dcd05517889efcb658acb00b805 100644 +--- a/src/main/java/io/papermc/paper/util/MCUtil.java ++++ b/src/main/java/io/papermc/paper/util/MCUtil.java +@@ -103,6 +103,20 @@ public final class MCUtil { + run.run(); + } + ++ // Paper start - avoid setting NaN values ++ public static double sanitizeNanInf(final double value, final double defaultValue) { ++ return Double.isNaN(value) || Double.isInfinite(value) ? defaultValue : value; ++ } ++ ++ public static Vec3 sanitizeNanInf(final Vec3 vec3, final double defaultValue) { ++ return new Vec3( ++ sanitizeNanInf(vec3.x, defaultValue), ++ sanitizeNanInf(vec3.y, defaultValue), ++ sanitizeNanInf(vec3.z, defaultValue) ++ ); ++ } ++ // Paper end - avoid setting NaN values ++ + public static T ensureMain(Supplier run) { + return ensureMain(null, run); + } diff --git a/leaf-server/paper-patches/features/0036-PaperPR-Fix-unnecessary-map-data-saves.patch b/leaf-server/paper-patches/features/0036-PaperPR-Fix-unnecessary-map-data-saves.patch new file mode 100644 index 00000000..44b8003a --- /dev/null +++ b/leaf-server/paper-patches/features/0036-PaperPR-Fix-unnecessary-map-data-saves.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dqu1J +Date: Tue, 29 Apr 2025 01:35:45 -0400 +Subject: [PATCH] PaperPR: Fix unnecessary map data saves + +Original license: GPLv3 +Original project: https://github.com/PaperMC/Paper +Paper pull request: https://github.com/PaperMC/Paper/pull/12296 + +Fixes Paper#12295 + +Currently, MapCanvas API unnecessarily causes map data file to save. + +The API calls this.mapView.worldMap.setColorsDirty() to mark colors dirty for players, however this has the side-effect of saving the map .dat files as well: +``` +public void setColorsDirty(int x, int z) { + this.setDirty(); // This saves the data file! + + for (MapItemSavedData.HoldingPlayer holdingPlayer : this.carriedBy) { + holdingPlayer.markColorsDirty(x, z); // This is what the API wants to do! + } +} +``` +This causes unnecessary lag during world saves, which scales with the amount of maps handled with API since last save. On servers that heavily rely on imageonmap-like plugins, it can cause lag spikes of dozens of seconds. + +This PR changes this method to add a boolean argument to the method that determines whether the file is saved or not, which is used by the API. An overload is added for compatibility, and for vanilla. + +diff --git a/src/main/java/org/bukkit/craftbukkit/map/CraftMapCanvas.java b/src/main/java/org/bukkit/craftbukkit/map/CraftMapCanvas.java +index 94c0e6d748cec58ffbefd4b7eeb784beedfa6fb3..7cdab2d347eee0af2abe9b1deddce6ce6521f401 100644 +--- a/src/main/java/org/bukkit/craftbukkit/map/CraftMapCanvas.java ++++ b/src/main/java/org/bukkit/craftbukkit/map/CraftMapCanvas.java +@@ -64,7 +64,7 @@ public class CraftMapCanvas implements MapCanvas { + return; + if (this.buffer[y * 128 + x] != color) { + this.buffer[y * 128 + x] = color; +- this.mapView.worldMap.setColorsDirty(x, y); ++ this.mapView.worldMap.setColorsDirty(x, y, false); // Paper - Fix unnecessary map data saves + } + } + +@@ -141,8 +141,8 @@ public class CraftMapCanvas implements MapCanvas { + } + + // Mark all colors within the image as dirty +- this.mapView.worldMap.setColorsDirty(destX, destY); +- this.mapView.worldMap.setColorsDirty(destX + effectiveWidth - 1, destY + effectiveHeight - 1); ++ this.mapView.worldMap.setColorsDirty(destX, destY, false); // Paper - Fix unnecessary map data saves ++ this.mapView.worldMap.setColorsDirty(destX + effectiveWidth - 1, destY + effectiveHeight - 1, false); // Paper - Fix unnecessary map data saves + // Paper end + } + From 3c2dc1ae62d339f73a2e4ee2c5c249adadde912a Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Thu, 1 May 2025 15:06:14 -0400 Subject: [PATCH 36/45] CHRSTN - Never Change Genre: EDM Progressive House --- .../features/0162-paw-optimization.patch | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 leaf-server/minecraft-patches/features/0162-paw-optimization.patch diff --git a/leaf-server/minecraft-patches/features/0162-paw-optimization.patch b/leaf-server/minecraft-patches/features/0162-paw-optimization.patch new file mode 100644 index 00000000..f67fcc72 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0162-paw-optimization.patch @@ -0,0 +1,214 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Thu, 24 Apr 2025 16:36:16 -0400 +Subject: [PATCH] paw optimization + +Some random optimizations + +- Remove Paper's dead code +- Only set shuffle random seed if is really used +- Secret patches (WIP) + +diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java +index f3e9de8716f5e1a72ec465ee897c8f0413f7b1c3..f998cf8d70302a21289de4d84b46d322d0b8a8fe 100644 +--- a/net/minecraft/network/Connection.java ++++ b/net/minecraft/network/Connection.java +@@ -617,13 +617,7 @@ public class Connection extends SimpleChannelInboundHandler> { + if (!(this.packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginPacketListener) + || loginPacketListener.state != net.minecraft.server.network.ServerLoginPacketListenerImpl.State.VERIFYING + || Connection.joinAttemptsThisTick++ < MAX_PER_TICK) { +- // Paper start - detailed watchdog information +- net.minecraft.network.protocol.PacketUtils.packetProcessing.push(this.packetListener); +- try { + tickablePacketListener.tick(); +- } finally { +- net.minecraft.network.protocol.PacketUtils.packetProcessing.pop(); +- } // Paper end - detailed watchdog information + } // Paper end - Buffer joins to world + } + +diff --git a/net/minecraft/network/protocol/PacketUtils.java b/net/minecraft/network/protocol/PacketUtils.java +index 4535858701b2bb232b9d2feb2af6551526232ddc..e65c62dbe4c1560ae153e4c4344e9194c783a2f4 100644 +--- a/net/minecraft/network/protocol/PacketUtils.java ++++ b/net/minecraft/network/protocol/PacketUtils.java +@@ -21,8 +21,6 @@ public class PacketUtils { + public static void ensureRunningOnSameThread(Packet packet, T processor, BlockableEventLoop executor) throws RunningOnDifferentThreadException { + if (!executor.isSameThread()) { + executor.executeIfPossible(() -> { +- packetProcessing.push(processor); // Paper - detailed watchdog information +- try { // Paper - detailed watchdog information + if (processor instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl serverCommonPacketListener && serverCommonPacketListener.processedDisconnect) return; // Paper - Don't handle sync packets for kicked players + if (processor.shouldHandleMessage(packet)) { + try { +@@ -37,12 +35,6 @@ public class PacketUtils { + } else { + LOGGER.debug("Ignoring packet due to disconnection: {}", packet); + } +- // Paper start - detailed watchdog information +- } finally { +- totalMainThreadPacketsProcessed.getAndIncrement(); +- packetProcessing.pop(); +- } +- // Paper end - detailed watchdog information + }); + throw RunningOnDifferentThreadException.RUNNING_ON_DIFFERENT_THREAD; + } +@@ -69,22 +61,4 @@ public class PacketUtils { + + packetListener.fillCrashReport(crashReport); + } +- +- // Paper start - detailed watchdog information +- public static final java.util.concurrent.ConcurrentLinkedDeque packetProcessing = new java.util.concurrent.ConcurrentLinkedDeque<>(); +- static final java.util.concurrent.atomic.AtomicLong totalMainThreadPacketsProcessed = new java.util.concurrent.atomic.AtomicLong(); +- +- public static long getTotalProcessedPackets() { +- return totalMainThreadPacketsProcessed.get(); +- } +- +- public static java.util.List getCurrentPacketProcessors() { +- java.util.List listeners = new java.util.ArrayList<>(4); +- for (PacketListener listener : packetProcessing) { +- listeners.add(listener); +- } +- +- return listeners; +- } +- // Paper end - detailed watchdog information + } +diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java +index d75f85208da0c7424fc95ae0d8ebb0a725dda0a7..4c0a6dcee5e63f788e532208ef3485726eedf8cc 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -504,9 +504,10 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + try { + this.collectTickingChunks(list); + // Paper start - chunk tick iteration optimisation +- this.shuffleRandom.setSeed(this.level.random.nextLong()); +- if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) ++ if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { ++ this.shuffleRandom.setSeed(this.level.random.nextLong()); // Leaf - paw optimization - Only set seed if is really used + Util.shuffle(list, this.shuffleRandom); // Paper - Optional per player mob spawns; do not need this when per-player is enabled ++ } + // Paper end - chunk tick iteration optimisation + this.tickChunks(l, list); // Gale - Purpur - remove vanilla profiler + } finally { +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index fbfb35dad8b07c31f967d33fb04cfcfc94557d72..ba1dd51e7187a80e8438e46383257c22f5382130 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -1387,13 +1387,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Paper end - log detailed entity tick information + + public void tickNonPassenger(Entity entity) { +- // Paper start - log detailed entity tick information + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread("Cannot tick an entity off-main"); +- try { +- if (currentlyTickingEntity.get() == null) { +- currentlyTickingEntity.lazySet(entity); +- } +- // Paper end - log detailed entity tick information + entity.setOldPosAndRot(); + entity.tickCount++; + entity.totalEntityAge++; // Paper - age-like counter for all entities +@@ -1406,13 +1400,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + for (Entity entity1 : entity.getPassengers()) { + this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2 + } +- // Paper start - log detailed entity tick information +- } finally { +- if (currentlyTickingEntity.get() == entity) { +- currentlyTickingEntity.lazySet(null); +- } +- } +- // Paper end - log detailed entity tick information + } + + private void tickPassenger(Entity ridingEntity, Entity passengerEntity, final boolean isActive) { // Paper - EAR 2 +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index a81983182ee3e3b874ba83ddf9bbc6ea772a2997..5296872ef06e025af5d2a830eff0e6b38d2747aa 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -1143,31 +1143,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + return this.onGround; + } + +- // Paper start - detailed watchdog information +- public final Object posLock = new Object(); // Paper - log detailed entity tick information +- +- private Vec3 moveVector; +- private double moveStartX; +- private double moveStartY; +- private double moveStartZ; +- +- public final Vec3 getMoveVector() { +- return this.moveVector; +- } +- +- public final double getMoveStartX() { +- return this.moveStartX; +- } +- +- public final double getMoveStartY() { +- return this.moveStartY; +- } +- +- public final double getMoveStartZ() { +- return this.moveStartZ; +- } +- // Paper end - detailed watchdog information +- + public void move(MoverType type, Vec3 movement) { + // Gale start - VMP - skip entity move if movement is zero + if (!this.boundingBoxChanged && movement.equals(Vec3.ZERO)) { +@@ -1175,16 +1150,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + // Gale end - VMP - skip entity move if movement is zero + final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity +- // Paper start - detailed watchdog information + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread("Cannot move an entity off-main"); +- synchronized (this.posLock) { +- this.moveStartX = this.getX(); +- this.moveStartY = this.getY(); +- this.moveStartZ = this.getZ(); +- this.moveVector = movement; +- } +- try { +- // Paper end - detailed watchdog information + if (this.noPhysics) { + this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); + } else { +@@ -1305,13 +1271,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + // Gale end - skip negligible planar movement multiplication + } + } +- // Paper start - detailed watchdog information +- } finally { +- synchronized (this.posLock) { // Paper +- this.moveVector = null; +- } // Paper +- } +- // Paper end - detailed watchdog information + } + + private void applyMovementEmissionAndPlaySound(Entity.MovementEmission movementEmission, Vec3 movement, BlockPos pos, BlockState state) { +@@ -4833,9 +4796,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + public void setDeltaMovement(Vec3 deltaMovement) { +- synchronized (this.posLock) { // Paper - detailed watchdog information + this.deltaMovement = deltaMovement; +- } // Paper - detailed watchdog information + } + + public void addDeltaMovement(Vec3 addend) { +@@ -4941,9 +4902,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + // Paper end - Fix MC-4 + if (this.position.x != x || this.position.y != y || this.position.z != z) { +- synchronized (this.posLock) { // Paper - detailed watchdog information + this.position = new Vec3(x, y, z); +- } // Paper - detailed watchdog information + int floor = Mth.floor(x); + int floor1 = Mth.floor(y); + int floor2 = Mth.floor(z); From 38a433852a9704aa9af62898ab8990c6e1ff1be7 Mon Sep 17 00:00:00 2001 From: Taiyou06 Date: Thu, 1 May 2025 21:32:38 +0200 Subject: [PATCH 37/45] Revert "fix async chunk sending buffer (#301)" This reverts commit bd3eb337d04abb9d4ed2ca69058fb819647b26e8. --- .../features/0132-Async-chunk-sending.patch | 154 +----------------- .../features/0157-Optimise-chunkUnloads.patch | 6 +- .../features/0034-Async-chunk-sending.patch | 124 -------------- 3 files changed, 9 insertions(+), 275 deletions(-) delete mode 100644 leaf-server/paper-patches/features/0034-Async-chunk-sending.patch diff --git a/leaf-server/minecraft-patches/features/0132-Async-chunk-sending.patch b/leaf-server/minecraft-patches/features/0132-Async-chunk-sending.patch index 76b36fb2..e8b716e1 100644 --- a/leaf-server/minecraft-patches/features/0132-Async-chunk-sending.patch +++ b/leaf-server/minecraft-patches/features/0132-Async-chunk-sending.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Async chunk sending diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -index a35e9fae8f8da0c42f0616c4f78dc396492673aa..c0f14d2bc30a186511bafddc2e80f29b686887c5 100644 +index a35e9fae8f8da0c42f0616c4f78dc396492673aa..af49117695c0785033b984ff91550e2fccbbc5e6 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -@@ -411,19 +411,98 @@ public final class RegionizedPlayerChunkLoader { +@@ -411,19 +411,91 @@ public final class RegionizedPlayerChunkLoader { this.delayedTicketOps.addLast(op); } @@ -29,6 +29,7 @@ index a35e9fae8f8da0c42f0616c4f78dc396492673aa..c0f14d2bc30a186511bafddc2e80f29b + // Already in our sent list - silently return instead of throwing an exception + return; + } ++ + // Get the chunk now, as we need it for both sync and async paths + final LevelChunk chunk = ((ChunkSystemLevel) this.world).moonrise$getFullChunkIfLoaded(chunkX, chunkZ); + if (chunk == null) { @@ -36,12 +37,13 @@ index a35e9fae8f8da0c42f0616c4f78dc396492673aa..c0f14d2bc30a186511bafddc2e80f29b + this.sentChunks.remove(chunkKey); + return; + } - ++ + // Try to mark the chunk as received by this player + try { + // This part needs to remain on the main thread as it affects shared state + ((ChunkSystemServerLevel) this.world).moonrise$getChunkTaskScheduler().chunkHolderManager + .getChunkHolder(chunkX, chunkZ).vanillaChunkHolder.moonrise$addReceivedChunk(this.player); + + // Call onChunkWatch on the main thread as it might affect server state PlatformHooks.get().onChunkWatch(this.world, chunk, this.player); - PlayerChunkSender.sendChunk(this.player.connection, this.world, chunk); @@ -56,13 +58,6 @@ index a35e9fae8f8da0c42f0616c4f78dc396492673aa..c0f14d2bc30a186511bafddc2e80f29b + // Check if async chunk sending is enabled + if (org.dreeam.leaf.config.modules.async.AsyncChunkSend.enabled) { + // Async implementation -+ var heightmaps = new net.minecraft.nbt.CompoundTag(); -+ for (var entry : chunk.getHeightmaps()) { -+ if (entry.getKey().sendToClient()) { -+ heightmaps.put(entry.getKey().getSerializationKey(), new net.minecraft.nbt.LongArrayTag(entry.getValue().getRawData())); -+ } -+ } -+ var blockEntities = chunk.blockEntities.values().toArray(new net.minecraft.world.level.block.entity.BlockEntity[0]); + net.minecraft.Util.backgroundExecutor().execute(() -> { + try { + final net.minecraft.server.network.ServerGamePacketListenerImpl connection = this.player.connection; @@ -71,9 +66,7 @@ index a35e9fae8f8da0c42f0616c4f78dc396492673aa..c0f14d2bc30a186511bafddc2e80f29b + // Create the packet with anti-xray control flag + final net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket packet = new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket( + chunk, serverLevel.getLightEngine(), null, null, -+ serverLevel.chunkPacketBlockController.shouldModify(this.player, chunk), -+ heightmaps, -+ blockEntities ++ serverLevel.chunkPacketBlockController.shouldModify(this.player, chunk) + ); + + // Let the main thread handle the anti-xray processing @@ -113,138 +106,3 @@ index a35e9fae8f8da0c42f0616c4f78dc396492673aa..c0f14d2bc30a186511bafddc2e80f29b private void sendUnloadChunk(final int chunkX, final int chunkZ) { if (!this.sentChunks.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java -index 9e321ef1c3d5803519b243685f4ee598dc0cf640..c981801171307e571388e6e810840ae2525c5d10 100644 ---- a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java -+++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java -@@ -26,6 +26,7 @@ public class ClientboundLevelChunkPacketData { - private static final int TWO_MEGABYTES = 2097152; - private final CompoundTag heightmaps; - private final byte[] buffer; -+ private final int bufferLength; - private final List blockEntitiesData; - // Paper start - Handle oversized block entities in chunks - private final java.util.List> extraPackets = new java.util.ArrayList<>(); -@@ -52,6 +53,7 @@ public class ClientboundLevelChunkPacketData { - } - - this.buffer = new byte[calculateChunkSize(levelChunk)]; -+ this.bufferLength = this.buffer.length; // Leaf - // Paper start - Anti-Xray - Add chunk packet info - if (chunkPacketInfo != null) { - chunkPacketInfo.setBuffer(this.buffer); -@@ -74,6 +76,51 @@ public class ClientboundLevelChunkPacketData { - } - } - -+ // Leaf start - Async chunk sending -+ public ClientboundLevelChunkPacketData( -+ LevelChunk levelChunk, -+ io.papermc.paper.antixray.ChunkPacketInfo chunkPacketInfo, -+ CompoundTag heightmaps, -+ BlockEntity[] blockEntities -+ ) { -+ this.heightmaps = heightmaps; -+ var buffer = new FriendlyByteBuf(Unpooled.buffer(calculateChunkSize(levelChunk) + 128)); -+ var sections = levelChunk.getSections(); -+ var sectionLength = sections.length; -+ for (int i = 0; i < sectionLength; i++) { -+ LevelChunkSection section = sections[i]; -+ synchronized (section.getStates()) { -+ buffer.writeShort(section.nonEmptyBlockCount()); -+ section.getStates().write(buffer, chunkPacketInfo, i); -+ } -+ section.getBiomes().write(buffer, null, i); -+ } -+ this.buffer = buffer.array(); -+ this.bufferLength = buffer.writerIndex(); -+ -+ // Paper start - Anti-Xray - Add chunk packet info -+ if (chunkPacketInfo != null) { -+ chunkPacketInfo.setBuffer(this.buffer); -+ chunkPacketInfo.setLength(this.bufferLength); -+ } -+ this.blockEntitiesData = Lists.newArrayList(); -+ int totalTileEntities = 0; // Paper - Handle oversized block entities in chunks -+ -+ for (BlockEntity blockEntity : blockEntities) { -+ // Paper start - Handle oversized block entities in chunks -+ if (++totalTileEntities > BLOCK_ENTITY_LIMIT) { -+ net.minecraft.network.protocol.Packet packet = blockEntity.getUpdatePacket(); -+ if (packet != null) { -+ this.extraPackets.add(packet); -+ continue; -+ } -+ } -+ // Paper end - Handle oversized block entities in chunks -+ this.blockEntitiesData.add(ClientboundLevelChunkPacketData.BlockEntityInfo.create(blockEntity)); -+ } -+ } -+ // Leaf end - Async chunk sending -+ - public ClientboundLevelChunkPacketData(RegistryFriendlyByteBuf buffer, int x, int z) { - this.heightmaps = buffer.readNbt(); - if (this.heightmaps == null) { -@@ -84,6 +131,7 @@ public class ClientboundLevelChunkPacketData { - throw new RuntimeException("Chunk Packet trying to allocate too much memory on read."); - } else { - this.buffer = new byte[varInt]; -+ this.bufferLength = this.buffer.length; // Leaf - buffer.readBytes(this.buffer); - this.blockEntitiesData = ClientboundLevelChunkPacketData.BlockEntityInfo.LIST_STREAM_CODEC.decode(buffer); - } -@@ -92,8 +140,8 @@ public class ClientboundLevelChunkPacketData { - - public void write(RegistryFriendlyByteBuf buffer) { - buffer.writeNbt(this.heightmaps); -- buffer.writeVarInt(this.buffer.length); -- buffer.writeBytes(this.buffer); -+ buffer.writeVarInt(this.bufferLength); // Leaf -+ buffer.writeBytes(this.buffer, 0, this.bufferLength); // Leaf - ClientboundLevelChunkPacketData.BlockEntityInfo.LIST_STREAM_CODEC.encode(buffer, this.blockEntitiesData); - } - -diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java -index 8578d1f78ddd1bb75f3230f04bfaa35af9f5f822..4eeb4967120b1c2cf13d2b4a8c07175fb4d98012 100644 ---- a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java -+++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java -@@ -45,6 +45,26 @@ public class ClientboundLevelChunkWithLightPacket implements Packet chunkPacketInfo = modifyBlocks ? chunk.getLevel().chunkPacketBlockController.getChunkPacketInfo(this, chunk) : null; // Paper - Ant-Xray -+ this.chunkData = new ClientboundLevelChunkPacketData(chunk, chunkPacketInfo, heightmaps, blockEntities); // Paper - Anti-Xray -+ this.lightData = new ClientboundLightUpdatePacketData(pos, lightEngine, skyLight, blockLight); -+ chunk.getLevel().chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks -+ } -+ // Leaf end - Async chunk sending -+ - private ClientboundLevelChunkWithLightPacket(RegistryFriendlyByteBuf buffer) { - this.x = buffer.readInt(); - this.z = buffer.readInt(); -diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java -index b8ac6a9ba7b56ccd034757f7d135d272b8e69e90..f85c1c3ab8cc0d0dafa6df8536318e3443265d2b 100644 ---- a/net/minecraft/world/level/chunk/LevelChunkSection.java -+++ b/net/minecraft/world/level/chunk/LevelChunkSection.java -@@ -49,6 +49,8 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ - } - // Paper end - block counting - -+ public final int nonEmptyBlockCount() { return this.nonEmptyBlockCount; } // Leaf -+ - private LevelChunkSection(LevelChunkSection section) { - this.nonEmptyBlockCount = section.nonEmptyBlockCount; - this.tickingBlockCount = section.tickingBlockCount; diff --git a/leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch b/leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch index 03d616cf..61b8d35b 100644 --- a/leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch +++ b/leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch @@ -159,7 +159,7 @@ index 4ca68a903e67606fc4ef0bfa9862a73797121c8b..bed3a64388bb43e47c2ba4e67f7dde5b public static final class SaveState { diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java -index f85c1c3ab8cc0d0dafa6df8536318e3443265d2b..449f180c49f191786c5f253baf04ddd9b72835f7 100644 +index b8ac6a9ba7b56ccd034757f7d135d272b8e69e90..dc158e981199b507531af810ff9ced3ca717e39e 100644 --- a/net/minecraft/world/level/chunk/LevelChunkSection.java +++ b/net/minecraft/world/level/chunk/LevelChunkSection.java @@ -24,6 +24,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ @@ -170,7 +170,7 @@ index f85c1c3ab8cc0d0dafa6df8536318e3443265d2b..449f180c49f191786c5f253baf04ddd9 // Paper start - block counting private static final it.unimi.dsi.fastutil.shorts.ShortArrayList FULL_LIST = new it.unimi.dsi.fastutil.shorts.ShortArrayList(16*16*16); -@@ -137,6 +138,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ +@@ -135,6 +136,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ // Paper end - block counting public BlockState setBlockState(int x, int y, int z, BlockState state, boolean useLocks) { @@ -178,7 +178,7 @@ index f85c1c3ab8cc0d0dafa6df8536318e3443265d2b..449f180c49f191786c5f253baf04ddd9 BlockState blockState; if (useLocks) { blockState = this.states.getAndSet(x, y, z, state); -@@ -330,7 +332,32 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ +@@ -328,7 +330,32 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ this.biomes = palettedContainer; } diff --git a/leaf-server/paper-patches/features/0034-Async-chunk-sending.patch b/leaf-server/paper-patches/features/0034-Async-chunk-sending.patch deleted file mode 100644 index b52674a1..00000000 --- a/leaf-server/paper-patches/features/0034-Async-chunk-sending.patch +++ /dev/null @@ -1,124 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: hayanesuru -Date: Sun, 27 Apr 2025 21:19:30 +0800 -Subject: [PATCH] Async chunk sending - - -diff --git a/src/main/java/io/papermc/paper/antixray/BitStorageReader.java b/src/main/java/io/papermc/paper/antixray/BitStorageReader.java -index c27703775c845a8b0bd1b0cb8f05eb736d8a813c..7e72c910232b85b411ecba1fa0acdf7f81c85418 100644 ---- a/src/main/java/io/papermc/paper/antixray/BitStorageReader.java -+++ b/src/main/java/io/papermc/paper/antixray/BitStorageReader.java -@@ -8,11 +8,18 @@ public final class BitStorageReader { - private int longInBufferIndex; - private int bitInLongIndex; - private long current; -+ private int length; // Leaf - - public void setBuffer(byte[] buffer) { - this.buffer = buffer; - } - -+ // Leaf start -+ public void setLength(int length) { -+ this.length = length; -+ } -+ // Leaf end -+ - public void setBits(int bits) { - this.bits = bits; - mask = (1 << bits) - 1; -@@ -25,7 +32,7 @@ public final class BitStorageReader { - } - - private void init() { -- if (buffer.length > longInBufferIndex + 7) { -+ if (length > longInBufferIndex + 7) { // Leaf - current = ((((long) buffer[longInBufferIndex]) << 56) - | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48) - | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40) -diff --git a/src/main/java/io/papermc/paper/antixray/BitStorageWriter.java b/src/main/java/io/papermc/paper/antixray/BitStorageWriter.java -index 83412e0ddaade11eb7ac7b41bb8ae5b085802775..d521bd2275152575f5fe5038a817471026abccda 100644 ---- a/src/main/java/io/papermc/paper/antixray/BitStorageWriter.java -+++ b/src/main/java/io/papermc/paper/antixray/BitStorageWriter.java -@@ -9,11 +9,18 @@ public final class BitStorageWriter { - private int bitInLongIndex; - private long current; - private boolean dirty; -+ private int length; // Leaf - - public void setBuffer(byte[] buffer) { - this.buffer = buffer; - } - -+ // Leaf start -+ public void setLength(int length) { -+ this.length = length; -+ } -+ // Leaf end -+ - public void setBits(int bits) { - this.bits = bits; - mask = (1L << bits) - 1; -@@ -26,7 +33,7 @@ public final class BitStorageWriter { - } - - private void init() { -- if (buffer.length > longInBufferIndex + 7) { -+ if (length > longInBufferIndex + 7) { // Leaf - current = ((((long) buffer[longInBufferIndex]) << 56) - | (((long) buffer[longInBufferIndex + 1] & 0xff) << 48) - | (((long) buffer[longInBufferIndex + 2] & 0xff) << 40) -@@ -41,7 +48,7 @@ public final class BitStorageWriter { - } - - public void flush() { -- if (dirty && buffer.length > longInBufferIndex + 7) { -+ if (dirty && length > longInBufferIndex + 7) { // Leaf - buffer[longInBufferIndex] = (byte) (current >> 56 & 0xff); - buffer[longInBufferIndex + 1] = (byte) (current >> 48 & 0xff); - buffer[longInBufferIndex + 2] = (byte) (current >> 40 & 0xff); -diff --git a/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java b/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java -index ee2d3a54d760f9c26542eab03c51651a30e279a0..bbf338e5e98a7b7d1c7cd986333df03f4aad4bb1 100644 ---- a/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java -+++ b/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java -@@ -227,6 +227,8 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo - boolean[] obfuscateTemp = null; - bitStorageReader.setBuffer(chunkPacketInfoAntiXray.getBuffer()); - bitStorageWriter.setBuffer(chunkPacketInfoAntiXray.getBuffer()); -+ bitStorageReader.setLength(chunkPacketInfoAntiXray.getLength()); -+ bitStorageWriter.setLength(chunkPacketInfoAntiXray.getLength()); - int numberOfBlocks = presetBlockStateBits.length; - // Keep the lambda expressions as simple as possible. They are used very frequently. - LayeredIntSupplier random = numberOfBlocks == 1 ? (() -> 0) : engineMode == EngineMode.OBFUSCATE_LAYER ? new LayeredIntSupplier() { -diff --git a/src/main/java/io/papermc/paper/antixray/ChunkPacketInfo.java b/src/main/java/io/papermc/paper/antixray/ChunkPacketInfo.java -index a33a4d45d478ededff27244fcb910d3f369f2151..26f0e4b43c2adc27e0ca2bafb7a1bab3fcf8f6cc 100644 ---- a/src/main/java/io/papermc/paper/antixray/ChunkPacketInfo.java -+++ b/src/main/java/io/papermc/paper/antixray/ChunkPacketInfo.java -@@ -13,6 +13,7 @@ public class ChunkPacketInfo { - private final int[] indexes; - private final Object[][] presetValues; - private byte[] buffer; -+ private int length; // Leaf - - public ChunkPacketInfo(ClientboundLevelChunkWithLightPacket chunkPacket, LevelChunk chunk) { - this.chunkPacket = chunkPacket; -@@ -36,8 +37,19 @@ public class ChunkPacketInfo { - return buffer; - } - -+ // Leaf start -+ public int getLength() { -+ return length; -+ } -+ // Leaf end -+ - public void setBuffer(byte[] buffer) { - this.buffer = buffer; -+ this.length = buffer.length; // Leaf -+ } -+ -+ public void setLength(int length) { -+ this.length = length; // Leaf - } - - public int getBits(int chunkSectionIndex) { From 1ae641bb76b7598c233203a5a13a9c1f35f595b8 Mon Sep 17 00:00:00 2001 From: Taiyou06 Date: Thu, 1 May 2025 22:09:41 +0200 Subject: [PATCH 38/45] [ci skip] rebuild patches --- .../minecraft-patches/features/0162-paw-optimization.patch | 6 +++--- ...h => 0034-PaperPR-Fix-save-load-NaN-Entity-Motion.patch} | 0 ...ch => 0035-PaperPR-Fix-unnecessary-map-data-saves.patch} | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename leaf-server/paper-patches/features/{0035-PaperPR-Fix-save-load-NaN-Entity-Motion.patch => 0034-PaperPR-Fix-save-load-NaN-Entity-Motion.patch} (100%) rename leaf-server/paper-patches/features/{0036-PaperPR-Fix-unnecessary-map-data-saves.patch => 0035-PaperPR-Fix-unnecessary-map-data-saves.patch} (100%) diff --git a/leaf-server/minecraft-patches/features/0162-paw-optimization.patch b/leaf-server/minecraft-patches/features/0162-paw-optimization.patch index f67fcc72..f3e96166 100644 --- a/leaf-server/minecraft-patches/features/0162-paw-optimization.patch +++ b/leaf-server/minecraft-patches/features/0162-paw-optimization.patch @@ -126,7 +126,7 @@ index fbfb35dad8b07c31f967d33fb04cfcfc94557d72..ba1dd51e7187a80e8438e46383257c22 private void tickPassenger(Entity ridingEntity, Entity passengerEntity, final boolean isActive) { // Paper - EAR 2 diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index a81983182ee3e3b874ba83ddf9bbc6ea772a2997..5296872ef06e025af5d2a830eff0e6b38d2747aa 100644 +index a81983182ee3e3b874ba83ddf9bbc6ea772a2997..4221e5322fa3a3ff6ab53946aa71d54144d2c4b2 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -1143,31 +1143,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @@ -192,7 +192,7 @@ index a81983182ee3e3b874ba83ddf9bbc6ea772a2997..5296872ef06e025af5d2a830eff0e6b3 } private void applyMovementEmissionAndPlaySound(Entity.MovementEmission movementEmission, Vec3 movement, BlockPos pos, BlockState state) { -@@ -4833,9 +4796,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4833,9 +4792,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public void setDeltaMovement(Vec3 deltaMovement) { @@ -202,7 +202,7 @@ index a81983182ee3e3b874ba83ddf9bbc6ea772a2997..5296872ef06e025af5d2a830eff0e6b3 } public void addDeltaMovement(Vec3 addend) { -@@ -4941,9 +4902,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4941,9 +4898,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } // Paper end - Fix MC-4 if (this.position.x != x || this.position.y != y || this.position.z != z) { diff --git a/leaf-server/paper-patches/features/0035-PaperPR-Fix-save-load-NaN-Entity-Motion.patch b/leaf-server/paper-patches/features/0034-PaperPR-Fix-save-load-NaN-Entity-Motion.patch similarity index 100% rename from leaf-server/paper-patches/features/0035-PaperPR-Fix-save-load-NaN-Entity-Motion.patch rename to leaf-server/paper-patches/features/0034-PaperPR-Fix-save-load-NaN-Entity-Motion.patch diff --git a/leaf-server/paper-patches/features/0036-PaperPR-Fix-unnecessary-map-data-saves.patch b/leaf-server/paper-patches/features/0035-PaperPR-Fix-unnecessary-map-data-saves.patch similarity index 100% rename from leaf-server/paper-patches/features/0036-PaperPR-Fix-unnecessary-map-data-saves.patch rename to leaf-server/paper-patches/features/0035-PaperPR-Fix-unnecessary-map-data-saves.patch From 445df88770c3aba1162192f082b8411a3d0f89d5 Mon Sep 17 00:00:00 2001 From: Taiyou06 Date: Thu, 1 May 2025 23:22:30 +0200 Subject: [PATCH 39/45] few Sakura patches --- README.md | 1 + ...tyList-implementation-to-BasicEntity.patch | 81 +++++++++++++++++++ ...check-inside-blocks-and-traverse-blo.patch | 68 ++++++++++++++++ .../leaf/util/map/BlockPosIterator.java | 69 ++++++++++++++++ 4 files changed, 219 insertions(+) create mode 100644 leaf-server/minecraft-patches/features/0163-Sakura-copy-EntityList-implementation-to-BasicEntity.patch create mode 100644 leaf-server/minecraft-patches/features/0164-Sakura-Optimise-check-inside-blocks-and-traverse-blo.patch create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/util/map/BlockPosIterator.java diff --git a/README.md b/README.md index 3cfa440f..9176fba9 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,7 @@ If these excellent projects hadn't appeared, Leaf wouldn't have become great. • Luminol
Nitori
Moonrise (during 1.21.1)
+ • Sakura

diff --git a/leaf-server/minecraft-patches/features/0163-Sakura-copy-EntityList-implementation-to-BasicEntity.patch b/leaf-server/minecraft-patches/features/0163-Sakura-copy-EntityList-implementation-to-BasicEntity.patch new file mode 100644 index 00000000..16a14fa4 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0163-Sakura-copy-EntityList-implementation-to-BasicEntity.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Samsuik +Date: Thu, 1 May 2025 22:28:50 +0200 +Subject: [PATCH] Sakura: copy-EntityList-implementation-to-BasicEntityList + + +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 b6af8da084c83ee38bb3ecea6a98feb0c1c74d2a..4311309f14c069f929ffe86bf0a91d7df3222828 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java +@@ -382,6 +382,13 @@ public final class ChunkEntitySlices { + + private E[] storage; + private int size; ++ // Sakura start - use methods from EntityList ++ private it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap entityToIndex = null; ++ private void setupIndexMap() { ++ this.entityToIndex = new it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap(2, 0.8f); ++ this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE); ++ } ++ // Sakura end - use methods from EntityList + + public BasicEntityList() { + this(0); +@@ -402,6 +409,7 @@ public final class ChunkEntitySlices { + private void resize() { + if (this.storage == me.titaniumtown.ArrayConstants.emptyEntityArray) { // Gale - JettPack - reduce array allocations + this.storage = (E[])new Entity[DEFAULT_CAPACITY]; ++ this.setupIndexMap(); // Sakura - use methods from EntityList + } else { + this.storage = Arrays.copyOf(this.storage, this.storage.length * 2); + } +@@ -415,6 +423,7 @@ public final class ChunkEntitySlices { + } else { + this.storage[idx] = entity; + } ++ this.entityToIndex.put(entity.getId(), idx); // Sakura - use methods from EntityList + } + + public int indexOf(final E entity) { +@@ -430,24 +439,32 @@ public final class ChunkEntitySlices { + } + + public boolean remove(final E entity) { +- final int idx = this.indexOf(entity); +- if (idx == -1) { ++ // Sakura start - use methods from EntityList ++ if (this.entityToIndex == null) { + return false; + } + +- final int size = --this.size; +- final E[] storage = this.storage; +- if (idx != size) { +- System.arraycopy(storage, idx + 1, storage, idx, size - idx); ++ final int index = this.entityToIndex.remove(entity.getId()); ++ if (index == Integer.MIN_VALUE) { ++ return false; + } + +- storage[size] = null; ++ // move the entity at the end to this index ++ final int endIndex = --this.size; ++ final E end = this.storage[endIndex]; ++ if (index != endIndex) { ++ // not empty after this call ++ this.entityToIndex.put(end.getId(), index); // update index ++ } ++ this.storage[index] = end; ++ this.storage[endIndex] = null; ++ // Sakura end - use methods from EntityList + + return true; + } + + public boolean has(final E entity) { +- return this.indexOf(entity) != -1; ++ return this.entityToIndex != null && this.entityToIndex.containsKey(entity.getId()); // Sakura - use methods from EntityList + } + } + diff --git a/leaf-server/minecraft-patches/features/0164-Sakura-Optimise-check-inside-blocks-and-traverse-blo.patch b/leaf-server/minecraft-patches/features/0164-Sakura-Optimise-check-inside-blocks-and-traverse-blo.patch new file mode 100644 index 00000000..053f1b90 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0164-Sakura-Optimise-check-inside-blocks-and-traverse-blo.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Thu, 1 May 2025 22:38:48 +0200 +Subject: [PATCH] Sakura: Optimise-check-inside-blocks-and-traverse-blocks + + +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index 4221e5322fa3a3ff6ab53946aa71d54144d2c4b2..4474639b0411236e208c542973084864365c544c 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -1670,6 +1670,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + private void checkInsideBlocks(List movements, Set blocksInside) { + if (this.isAffectedByBlocks()) { + LongSet set = this.visitedBlocks; ++ // Sakura start - optimise check inside blocks ++ int lastChunkX = Integer.MIN_VALUE; ++ int lastChunkZ = Integer.MIN_VALUE; ++ net.minecraft.world.level.chunk.ChunkAccess chunk = null; ++ // Sakura end - optimise check inside blocks + + for (Entity.Movement movement : movements) { + Vec3 vec3 = movement.from(); +@@ -1681,7 +1686,19 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + return; + } + +- BlockState blockState = this.level().getBlockState(blockPos); ++ // Sakura start - optimise check inside blocks ++ final int chunkX = blockPos.getX() >> 4; ++ final int chunkZ = blockPos.getZ() >> 4; ++ if (chunk == null || chunkX != lastChunkX || chunkZ != lastChunkZ) { ++ chunk = this.level.getChunkIfLoadedImmediately(chunkX, chunkZ); ++ if (chunk == null) { ++ continue; ++ } ++ lastChunkX = chunkX; ++ lastChunkZ = chunkZ; ++ } ++ final BlockState blockState = chunk.getBlockState(blockPos); ++ // Sakura end - optimise check inside blocks + if (!blockState.isAir() && set.add(blockPos.asLong())) { + try { + VoxelShape entityInsideCollisionShape = blockState.getEntityInsideCollisionShape(this.level(), blockPos); +diff --git a/net/minecraft/world/level/BlockGetter.java b/net/minecraft/world/level/BlockGetter.java +index 91865d7e78e15cc643a65de03045b90a52d6ec2a..03f82e60528738e89f195cfc59094f53156f5370 100644 +--- a/net/minecraft/world/level/BlockGetter.java ++++ b/net/minecraft/world/level/BlockGetter.java +@@ -214,10 +214,18 @@ public interface BlockGetter extends LevelHeightAccessor { + + static Iterable boxTraverseBlocks(Vec3 oldPosition, Vec3 position, AABB boundingBox) { + Vec3 vec3 = position.subtract(oldPosition); +- Iterable iterable = BlockPos.betweenClosed(boundingBox); ++ // Sakura start - optimise check inside blocks + if (vec3.lengthSqr() < Mth.square(0.99999F)) { +- return iterable; ++ return org.dreeam.leaf.util.map.BlockPosIterator.iterable(boundingBox); + } else { ++ final boolean xZero = vec3.x() == 0.0; ++ final boolean yZero = vec3.y() == 0.0; ++ final boolean zZero = vec3.z() == 0.0; ++ if (xZero && yZero || yZero && zZero || xZero && zZero) { ++ return org.dreeam.leaf.util.map.BlockPosIterator.traverseArea(vec3, boundingBox); ++ } ++ Iterable iterable = BlockPos.betweenClosed(boundingBox); ++ // Sakura end - optimise check inside blocks + Set set = new ObjectLinkedOpenHashSet<>(); + Vec3 minPosition = boundingBox.getMinPosition(); + Vec3 vec31 = minPosition.subtract(vec3); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/map/BlockPosIterator.java b/leaf-server/src/main/java/org/dreeam/leaf/util/map/BlockPosIterator.java new file mode 100644 index 00000000..bf269d54 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/map/BlockPosIterator.java @@ -0,0 +1,69 @@ +package org.dreeam.leaf.util.map; + +import com.google.common.collect.AbstractIterator; +import net.minecraft.core.BlockPos; +import net.minecraft.core.BlockPos.MutableBlockPos; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public final class BlockPosIterator extends AbstractIterator { + private final int startX; + private final int startY; + private final int startZ; + private final int endX; + private final int endY; + private final int endZ; + private @Nullable MutableBlockPos pos = null; + + public static Iterable iterable(AABB bb) { + return () -> new BlockPosIterator(bb); + } + + public static Iterable traverseArea(Vec3 vec, AABB boundingBox) { + double toTravel = Math.min(16.0 / vec.length(), 1.0); + Vec3 movement = vec.scale(toTravel); + AABB fromBB = boundingBox.move(-vec.x, -vec.y, -vec.z); + AABB searchArea = fromBB.expandTowards(movement); + return org.dreeam.leaf.util.map.BlockPosIterator.iterable(searchArea); + } + + public BlockPosIterator(AABB bb) { + this.startX = Mth.floor(bb.minX); + this.startY = Mth.floor(bb.minY); + this.startZ = Mth.floor(bb.minZ); + this.endX = Mth.floor(bb.maxX); + this.endY = Mth.floor(bb.maxY); + this.endZ = Mth.floor(bb.maxZ); + } + + @Override + protected BlockPos computeNext() { + MutableBlockPos pos = this.pos; + if (pos == null) { + return this.pos = new MutableBlockPos(this.startX, this.startY, this.startZ); + } else { + int x = pos.getX(); + int y = pos.getY(); + int z = pos.getZ(); + + if (y < this.endY) { + y += 1; + } else if (x < this.endX) { + x += 1; + y = this.startY; + } else if (z < this.endZ) { + z += 1; + x = this.startX; + } else { + return this.endOfData(); + } + + pos.set(x, y, z); + return pos; + } + } +} From 85c8a17e535baa108ce4300152203dcbe37b8d9f Mon Sep 17 00:00:00 2001 From: Taiyou06 Date: Thu, 1 May 2025 23:30:40 +0200 Subject: [PATCH 40/45] [ci skip] remove experimental tags from playerdata and target finding --- .../dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java | 3 --- .../dreeam/leaf/config/modules/async/AsyncTargetFinding.java | 3 --- 2 files changed, 6 deletions(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java index 96c5ad15..ebdad7ae 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java @@ -10,17 +10,14 @@ public class AsyncPlayerDataSave extends ConfigModules { return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-playerdata-save"; } - @Experimental public static boolean enabled = false; @Override public void onLoaded() { config.addCommentRegionBased(getBasePath(), """ - **Experimental feature, may have data lost in some circumstances!** Make PlayerData saving asynchronously.""", """ - **实验性功能, 在部分场景下可能丢失玩家数据!** 异步保存玩家数据."""); enabled = config.getBoolean(getBasePath() + ".enabled", enabled); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java index bd26abb4..eb2a5aaf 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java @@ -11,7 +11,6 @@ public class AsyncTargetFinding extends ConfigModules { return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-target-finding"; } - @Experimental public static boolean enabled = false; public static boolean alertOther = true; public static boolean searchBlock = true; @@ -22,11 +21,9 @@ public class AsyncTargetFinding extends ConfigModules { @Override public void onLoaded() { config.addCommentRegionBased(getBasePath(), """ - **Experimental feature** This moves the expensive entity and block search calculations to background thread while keeping the actual validation on the main thread.""", """ - **实验性功能** 这会将昂贵的实体目标搜索计算移至后台线程, 同时在主线程上保持实际的实体验证."""); if (asyncTargetFindingInitialized) { From c20485292df52f0aeb19bffa553309995c325e9f Mon Sep 17 00:00:00 2001 From: Taiyou06 Date: Fri, 2 May 2025 00:24:30 +0200 Subject: [PATCH 41/45] [ci skip] fix patch origin --- ...4-Sakura-Optimise-check-inside-blocks-and-traverse-blo.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leaf-server/minecraft-patches/features/0164-Sakura-Optimise-check-inside-blocks-and-traverse-blo.patch b/leaf-server/minecraft-patches/features/0164-Sakura-Optimise-check-inside-blocks-and-traverse-blo.patch index 053f1b90..e3ef4896 100644 --- a/leaf-server/minecraft-patches/features/0164-Sakura-Optimise-check-inside-blocks-and-traverse-blo.patch +++ b/leaf-server/minecraft-patches/features/0164-Sakura-Optimise-check-inside-blocks-and-traverse-blo.patch @@ -1,5 +1,5 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 +From: Samsuik Date: Thu, 1 May 2025 22:38:48 +0200 Subject: [PATCH] Sakura: Optimise-check-inside-blocks-and-traverse-blocks From 35d7a185f3d9fef161b29fba182d351de25e9a23 Mon Sep 17 00:00:00 2001 From: Taiyou06 Date: Fri, 2 May 2025 02:00:44 +0200 Subject: [PATCH 42/45] reduce unparking on AsyncGoalThread --- .../leaf/async/ai/AsyncGoalExecutor.java | 15 ++++++++++--- .../dreeam/leaf/async/ai/AsyncGoalThread.java | 21 ++++++++++++++++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java index a14c27e8..5ac7e6cb 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java @@ -18,6 +18,7 @@ public class AsyncGoalExecutor { private final ServerLevel serverLevel; private boolean dirty = false; private long tickCount = 0L; + private static final int SPIN_LIMIT = 100; public AsyncGoalExecutor(AsyncGoalThread thread, ServerLevel serverLevel) { this.serverLevel = serverLevel; @@ -38,8 +39,14 @@ public class AsyncGoalExecutor { public final void submit(int entityId) { if (!this.queue.send(entityId)) { - LockSupport.unpark(thread); + int spinCount = 0; while (!this.queue.send(entityId)) { + spinCount++; + // Unpark thread after some spinning to help clear the queue + if (spinCount > SPIN_LIMIT) { + unpark(); + spinCount = 0; + } Thread.onSpinWait(); } } @@ -52,15 +59,18 @@ public class AsyncGoalExecutor { } public final void midTick() { + boolean didWork = false; while (true) { int id = this.wake.recv(); if (id == Integer.MAX_VALUE) { break; } + didWork = true; Entity entity = this.serverLevel.getEntities().get(id); if (entity == null || !entity.isAlive() || !(entity instanceof Mob mob)) { continue; } + mob.tickingTarget = true; boolean a = mob.targetSelector.poll(); mob.tickingTarget = false; @@ -69,8 +79,7 @@ public class AsyncGoalExecutor { submit(id); } } - if ((tickCount & 3L) == 0L) unpark(); + if (didWork || (tickCount & 15L) == 0L) unpark(); tickCount += 1; } } - diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java index 9d8abe8c..7cab43cc 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java @@ -7,6 +7,8 @@ import net.minecraft.server.level.ServerLevel; import java.util.concurrent.locks.LockSupport; public class AsyncGoalThread extends Thread { + private static final int SPIN_TRIES = 1000; + public AsyncGoalThread(final MinecraftServer server) { super(() -> run(server), "Leaf Async Goal Thread"); this.setDaemon(true); @@ -16,21 +18,38 @@ public class AsyncGoalThread extends Thread { } private static void run(MinecraftServer server) { + int emptySpins = 0; + while (server.isRunning()) { - LockSupport.park(); + boolean didWork = false; for (ServerLevel level : server.getAllLevels()) { var exec = level.asyncGoalExecutor; + boolean levelWork = false; while (true) { int id = exec.queue.recv(); if (id == Integer.MAX_VALUE) { break; } + levelWork = true; if (exec.wake(id)) { while (!exec.wake.send(id)) { Thread.onSpinWait(); } } } + didWork |= levelWork; + } + // Adaptive parking + if (didWork) { + emptySpins = 0; // Reset counter when work was done + } else { + emptySpins++; + if (emptySpins > SPIN_TRIES) { + LockSupport.park(); // Only park after several empty spins + emptySpins = 0; + } else { + Thread.onSpinWait(); // Yield to other threads but don't park + } } } } From e3731721df49302cc36e3787318bf4c6535d0eb2 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Thu, 1 May 2025 21:01:29 -0700 Subject: [PATCH 43/45] marks AsyncGoalThread non-daemon --- .../src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java index 7cab43cc..12b78169 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java @@ -11,7 +11,7 @@ public class AsyncGoalThread extends Thread { public AsyncGoalThread(final MinecraftServer server) { super(() -> run(server), "Leaf Async Goal Thread"); - this.setDaemon(true); + this.setDaemon(false); this.setUncaughtExceptionHandler(Util::onThreadException); this.setPriority(Thread.NORM_PRIORITY - 1); this.start(); From f1df5351caae33b4b93927c33405a5b4be45dc09 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Fri, 2 May 2025 16:13:19 -0400 Subject: [PATCH 44/45] Update PlayerInventoryOverflowEvent (#304) * Add configurable option in config for PlayerInventoryOverflowEvent and able to define the class name of listener * Update checking method for overflow items handling logic, only fire event when actual listener is listening to it. --- .../0011-PlayerInventoryOverflowEvent.patch | 34 ++++++++++++++- .../0030-PlayerInventoryOverflowEvent.patch | 41 ++++++++++++++++--- .../ConfigurableInventoryOverflowEvent.java | 32 +++++++++++++++ 3 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableInventoryOverflowEvent.java diff --git a/leaf-api/paper-patches/features/0011-PlayerInventoryOverflowEvent.patch b/leaf-api/paper-patches/features/0011-PlayerInventoryOverflowEvent.patch index fb05b9c6..f34ac413 100644 --- a/leaf-api/paper-patches/features/0011-PlayerInventoryOverflowEvent.patch +++ b/leaf-api/paper-patches/features/0011-PlayerInventoryOverflowEvent.patch @@ -4,12 +4,40 @@ Date: Wed, 19 Feb 2025 00:34:16 -0500 Subject: [PATCH] PlayerInventoryOverflowEvent +diff --git a/src/main/java/org/bukkit/event/HandlerList.java b/src/main/java/org/bukkit/event/HandlerList.java +index 64d8916a8ca1cc5678a34c17a8bbbff45323beb0..0a1f989a35e0f2e878176e273e9f3b65b96bc67b 100644 +--- a/src/main/java/org/bukkit/event/HandlerList.java ++++ b/src/main/java/org/bukkit/event/HandlerList.java +@@ -66,6 +66,7 @@ public class HandlerList { + h.handlers = null; + } + } ++ org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent.isListeningInvOverflowCached = -1; // Leaf - PlayerInventoryOverflowEvent + } + } + +@@ -79,6 +80,7 @@ public class HandlerList { + for (HandlerList h : allLists) { + h.unregister(plugin); + } ++ org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent.isListeningInvOverflowCached = -1; // Leaf - PlayerInventoryOverflowEvent + } + } + +@@ -92,6 +94,7 @@ public class HandlerList { + for (HandlerList h : allLists) { + h.unregister(listener); + } ++ org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent.isListeningInvOverflowCached = -1; // Leaf - PlayerInventoryOverflowEvent + } + } + diff --git a/src/main/java/org/dreeam/leaf/event/player/PlayerInventoryOverflowEvent.java b/src/main/java/org/dreeam/leaf/event/player/PlayerInventoryOverflowEvent.java new file mode 100644 -index 0000000000000000000000000000000000000000..44c65eb6c503b94ac73d2b2169359be1b4810c98 +index 0000000000000000000000000000000000000000..eae76671190ef84529c0dd503263e43a15a74e8a --- /dev/null +++ b/src/main/java/org/dreeam/leaf/event/player/PlayerInventoryOverflowEvent.java -@@ -0,0 +1,63 @@ +@@ -0,0 +1,65 @@ +package org.dreeam.leaf.event.player; + +import org.bukkit.entity.Player; @@ -36,6 +64,8 @@ index 0000000000000000000000000000000000000000..44c65eb6c503b94ac73d2b2169359be1 + + private static final HandlerList HANDLER_LIST = new HandlerList(); + ++ public static short isListeningInvOverflowCached = -1; ++ + private final Inventory inventory; + private final Map overflowItemStacks; + diff --git a/leaf-server/paper-patches/features/0030-PlayerInventoryOverflowEvent.patch b/leaf-server/paper-patches/features/0030-PlayerInventoryOverflowEvent.patch index 6378bbb5..68270b11 100644 --- a/leaf-server/paper-patches/features/0030-PlayerInventoryOverflowEvent.patch +++ b/leaf-server/paper-patches/features/0030-PlayerInventoryOverflowEvent.patch @@ -5,23 +5,54 @@ Subject: [PATCH] PlayerInventoryOverflowEvent diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -index 8b4f8a475faafe3b8a479160888145c4aa603a27..6b0067e83920d32c62416a0c3d8ef2940ca7ed2b 100644 +index 8b4f8a475faafe3b8a479160888145c4aa603a27..2f8377ceefa29fbf0827f673c7bf6e1d3215e477 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -@@ -340,6 +340,16 @@ public class CraftInventory implements Inventory { +@@ -340,9 +340,47 @@ public class CraftInventory implements Inventory { } } } + + // Leaf start - PlayerInventoryOverflowEvent -+ if (org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent.getHandlerList().getRegisteredListeners().length > 0 -+ && !leftover.isEmpty() && this.inventory instanceof net.minecraft.world.entity.player.Inventory && this.inventory.getOwner() instanceof org.bukkit.entity.Player player) { ++ if (isListeningInventoryOverflowEvent() && !leftover.isEmpty() && this.inventory instanceof net.minecraft.world.entity.player.Inventory && this.inventory.getOwner() instanceof org.bukkit.entity.Player player) { + new org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent(player, leftover).callEvent(); + -+ leftover = new HashMap<>(); ++ return new HashMap<>(); + } + // Leaf end - PlayerInventoryOverflowEvent + return leftover; } ++ // Leaf start - PlayerInventoryOverflowEvent ++ private static boolean isListeningInventoryOverflowEvent() { ++ if (org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent.isListeningInvOverflowCached == -1) { ++ org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent.isListeningInvOverflowCached = 0; ++ ++ if (!org.dreeam.leaf.config.modules.gameplay.ConfigurableInventoryOverflowEvent.enabled) { ++ return false; ++ } ++ ++ org.bukkit.plugin.RegisteredListener[] listeners = org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent.getHandlerList().getRegisteredListeners(); ++ if (listeners.length == 1) { ++ if (listeners[0].getListener().getClass().getName().equals(org.dreeam.leaf.config.modules.gameplay.ConfigurableInventoryOverflowEvent.listenerClass)) { ++ org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent.isListeningInvOverflowCached = 1; ++ return true; ++ } ++ } else if (listeners.length > 1) { ++ for (org.bukkit.plugin.RegisteredListener registeredListener : listeners) { ++ if (registeredListener.getListener().getClass().getName().equals(org.dreeam.leaf.config.modules.gameplay.ConfigurableInventoryOverflowEvent.listenerClass)) { ++ org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent.isListeningInvOverflowCached = 1; ++ return true; ++ }; ++ } ++ } ++ } ++ ++ return org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent.isListeningInvOverflowCached == 1; ++ } ++ // Leaf end - PlayerInventoryOverflowEvent ++ + @Override + public HashMap removeItem(ItemStack... items) { + // Paper start diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableInventoryOverflowEvent.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableInventoryOverflowEvent.java new file mode 100644 index 00000000..3338c643 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/ConfigurableInventoryOverflowEvent.java @@ -0,0 +1,32 @@ +package org.dreeam.leaf.config.modules.gameplay; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class ConfigurableInventoryOverflowEvent extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".inventory-overflow-event"; + } + + public static boolean enabled = false; + public static String listenerClass = "com.example.package.PlayerInventoryOverflowEvent" ; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".enabled", enabled, config.pickStringRegionBased(""" + The event called when used plugin to Inventory#addItem + into player's inventory, and the inventory is full. + This is not recommended to use, please re-design to use the + returned map of Inventory#addItem method as soon as possible!""", + """ + 此事件将在插件使用 Inventory#addItem 方法 + 添加物品到玩家背包, 但是背包已满时调用. + 不建议使用此事件,请尽快迁移至使用 Inventory#addItem 方法 + 返回的 map""")); + listenerClass = config.getString(getBasePath() + ".listener-class", listenerClass, config.pickStringRegionBased(""" + The full class name of the listener which listens to this inventory overflow event.""", + """ + 监听此物品栏物品溢出事件的完整类名.""")); + } +} From 3f20ea5c714303aa8700a511fd9c1165d2cba9cd Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Fri, 2 May 2025 22:22:53 -0400 Subject: [PATCH 45/45] Cleanup --- build-data/leaf.at | 2 + ...-SparklyPaper-Parallel-world-ticking.patch | 11 +- ...hunk-retrieving-in-entity-fluid-push.patch | 6 +- ...ntLong2ReferenceChainedHashTable-wit.patch | 43 +- .../features/0154-Async-target-finding.patch | 600 ++++++++---------- ...timize-ThreadedTicketLevelPropagator.patch | 22 +- ...bEffectUtil-getDigSpeedAmplification.patch | 24 +- .../features/0157-Optimise-chunkUnloads.patch | 45 +- ...58-Optimize-BlockEntityType-isValid.patch} | 14 +- ...004-Pufferfish-Optimize-mob-spawning.patch | 33 +- .../leaf/async/ai/AsyncGoalExecutor.java | 18 +- .../dreeam/leaf/async/ai/AsyncGoalThread.java | 1 + .../java/org/dreeam/leaf/async/ai/Waker.java | 1 + .../leaf/async/world/PWTEventScheduler.java | 2 + .../config/modules/async/AsyncChunkSend.java | 3 +- .../modules/async/AsyncPathfinding.java | 9 +- .../modules/async/AsyncPlayerDataSave.java | 3 +- .../modules/async/AsyncTargetFinding.java | 1 + .../modules/async/MultithreadedTracker.java | 6 +- .../SparklyPaperParallelWorldTicking.java | 3 +- .../modules/gameplay/SmoothTeleport.java | 3 +- .../modules/network/ChatMessageSignature.java | 3 +- .../OptimizeNonFlushPacketSending.java | 6 +- .../config/modules/opt/DontSaveEntity.java | 3 +- .../leaf/util/map/BlockPosIterator.java | 1 + ...currentLong2ReferenceChainedHashTable.java | 500 ++++++++++----- .../dreeam/leaf/util/queue/SpscIntQueue.java | 1 + 27 files changed, 730 insertions(+), 634 deletions(-) rename leaf-server/minecraft-patches/features/{0158-optimize-BlockEntityType-isValid.patch => 0158-Optimize-BlockEntityType-isValid.patch} (79%) diff --git a/build-data/leaf.at b/build-data/leaf.at index 272f5d5a..227aec3d 100644 --- a/build-data/leaf.at +++ b/build-data/leaf.at @@ -1,6 +1,8 @@ # This file is auto generated, any changes may be overridden! # See CONTRIBUTING.md on how to add access transformers. protected net.minecraft.world.entity.Entity dimensions +protected net.minecraft.world.entity.ai.goal.RemoveBlockGoal blockToRemove +protected net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal getTargetConditions()Lnet/minecraft/world/entity/ai/targeting/TargetingConditions; protected-f net.minecraft.world.level.block.state.BlockBehaviour$BlockStateBase$Cache largeCollisionShape public net.minecraft.util.Mth SIN public net.minecraft.world.entity.Entity updateInWaterStateAndDoWaterCurrentPushing()V diff --git a/leaf-server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch b/leaf-server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch index 505f0d9c..35e26162 100644 --- a/leaf-server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch +++ b/leaf-server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch @@ -743,10 +743,10 @@ index b17c8a2f5294ac28cc05fb05c84a041b2c6c8721..0b8b4658dbbad1bacc13e97b4fc0cdce serverPlayer.connection = player.connection; serverPlayer.restoreFrom(player, keepInventory); diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 9bc978ca290ca772b0367e89b69fe16b502b0cd2..2f98d035b32d3a9b3366dbea0ac52b24d06dd373 100644 +index 9bc978ca290ca772b0367e89b69fe16b502b0cd2..334a47659ba75fade062bc79df3731d1e449114b 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java -@@ -3370,15 +3370,41 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -3370,15 +3370,40 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess if (this.portalProcess != null) { if (this.portalProcess.processPortalTeleportation(serverLevel, this, this.canUsePortal(false))) { this.setPortalCooldown(); @@ -760,12 +760,11 @@ index 9bc978ca290ca772b0367e89b69fe16b502b0cd2..2f98d035b32d3a9b3366dbea0ac52b24 + // TCRF SparklyPaper (Pathothingi) start - parallel world ticking + java.util.function.Consumer portalEntityTask = entity -> { + assert entity.portalProcess != null; -+ // Leaf start - Fix NPE when portalProcess becomes null before task execution ++ // Fix NPE when portalProcess becomes null before task execution ++ // Portal process was likely nulled out (e.g., expired) between scheduling and execution. + if (entity.portalProcess == null) { -+ // Portal process was likely nulled out (e.g., expired) between scheduling and execution. + return; + } -+ // Leaf end - Fix NPE + + if (entity.portalProcess.isParallelCancelledByPlugin()) { + entity.portalProcess = null; @@ -795,7 +794,7 @@ index 9bc978ca290ca772b0367e89b69fe16b502b0cd2..2f98d035b32d3a9b3366dbea0ac52b24 } else if (this.portalProcess.hasExpired()) { this.portalProcess = null; } -@@ -3908,6 +3934,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -3908,6 +3933,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } private Entity teleportCrossDimension(ServerLevel level, TeleportTransition teleportTransition) { diff --git a/leaf-server/minecraft-patches/features/0150-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch b/leaf-server/minecraft-patches/features/0150-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch index 069ad7ae..0e910c03 100644 --- a/leaf-server/minecraft-patches/features/0150-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch +++ b/leaf-server/minecraft-patches/features/0150-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch @@ -11,10 +11,10 @@ As part of: Airplane (https://github.com/TECHNOVE/Airplane) Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 2f98d035b32d3a9b3366dbea0ac52b24d06dd373..90879616842cc61d15854b07f56f6fcb89f11074 100644 +index 334a47659ba75fade062bc79df3731d1e449114b..0903508d2cd3c78602e62dbcff4aa70285bc4c4f 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java -@@ -4622,10 +4622,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4621,10 +4621,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess // Paper start - optimise collisions public boolean updateFluidHeightAndDoFluidPushing(final TagKey fluid, final double flowScale) { @@ -26,7 +26,7 @@ index 2f98d035b32d3a9b3366dbea0ac52b24d06dd373..90879616842cc61d15854b07f56f6fcb final AABB boundingBox = this.getBoundingBox().deflate(1.0E-3); final Level world = this.level; -@@ -4661,7 +4658,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4660,7 +4657,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { diff --git a/leaf-server/minecraft-patches/features/0153-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch b/leaf-server/minecraft-patches/features/0153-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch index 209b1f89..a0b98b40 100644 --- a/leaf-server/minecraft-patches/features/0153-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch +++ b/leaf-server/minecraft-patches/features/0153-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Replace ConcurrentLong2ReferenceChainedHashTable with custom diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java b/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java -index 7eafc5b7cba23d8dec92ecc1050afe3fd8c9e309..7a299490d502147ddfd533637da36ea0d2ac918a 100644 +index 7eafc5b7cba23d8dec92ecc1050afe3fd8c9e309..9b421f0681fe740520457951b1a1632ada59438a 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/queue/ChunkUnloadQueue.java @@ -16,7 +16,7 @@ public final class ChunkUnloadQueue { @@ -14,61 +14,58 @@ index 7eafc5b7cba23d8dec92ecc1050afe3fd8c9e309..7a299490d502147ddfd533637da36ea0 public final int coordinateShift; private final AtomicLong orderGenerator = new AtomicLong(); - private final ConcurrentLong2ReferenceChainedHashTable unloadSections = new ConcurrentLong2ReferenceChainedHashTable<>(); -+ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable unloadSections = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>(); ++ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable unloadSections = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>(); // Leaf - Replace ConcurrentLong2ReferenceChainedHashTable with custom map /* * Note: write operations do not occur in parallel for any given section. -@@ -32,8 +32,8 @@ public final class ChunkUnloadQueue { +@@ -32,8 +32,10 @@ public final class ChunkUnloadQueue { public List retrieveForAllRegions() { final List ret = new ArrayList<>(); - for (final Iterator> iterator = this.unloadSections.entryIterator(); iterator.hasNext();) { - final ConcurrentLong2ReferenceChainedHashTable.TableEntry entry = iterator.next(); ++ // Leaf start - Replace ConcurrentLong2ReferenceChainedHashTable with custom map + for (final Iterator> iterator = this.unloadSections.entryIterator(); iterator.hasNext(); ) { + final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable.TableEntry entry = iterator.next(); ++ // Leaf end - Replace ConcurrentLong2ReferenceChainedHashTable with custom map final long key = entry.getKey(); final UnloadSection section = entry.getValue(); final int sectionX = CoordinateUtils.getChunkX(key); -@@ -141,4 +141,4 @@ public final class ChunkUnloadQueue { - this.order = order; - } - } --} -\ No newline at end of file -+} diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -index 06ac3537f5655d048d770bb004243f207fad9faa..647354efeb18694ac56a542bf4a88be61136f6a8 100644 +index 06ac3537f5655d048d770bb004243f207fad9faa..e399fa14e4a28b4fd93d06c603fd70b6fadeb6b0 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -@@ -71,11 +71,11 @@ public final class ChunkHolderManager { +@@ -71,11 +71,13 @@ public final class ChunkHolderManager { private static final long PROBE_MARKER = Long.MIN_VALUE + 1; public final ReentrantAreaLock ticketLockArea; - private final ConcurrentLong2ReferenceChainedHashTable>> tickets = new ConcurrentLong2ReferenceChainedHashTable<>(); - private final ConcurrentLong2ReferenceChainedHashTable sectionToChunkToExpireCount = new ConcurrentLong2ReferenceChainedHashTable<>(); ++ // Leaf start - Replace ConcurrentLong2ReferenceChainedHashTable with custom map + private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable>> tickets = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>(); + private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable sectionToChunkToExpireCount = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>(); ++ // Leaf end - Replace ConcurrentLong2ReferenceChainedHashTable with custom map final ChunkUnloadQueue unloadQueue; - private final ConcurrentLong2ReferenceChainedHashTable chunkHolders = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(16384, 0.25f); -+ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable chunkHolders = org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable.createWithCapacity(16384, 0.25f); ++ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable chunkHolders = org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable.createWithCapacity(16384, 0.25f); // Leaf - Replace ConcurrentLong2ReferenceChainedHashTable with custom map private final ServerLevel world; private final ChunkTaskScheduler taskScheduler; private long currentTick; -@@ -1422,9 +1422,9 @@ public final class ChunkHolderManager { +@@ -1422,9 +1424,9 @@ public final class ChunkHolderManager { final JsonArray allTicketsJson = new JsonArray(); ret.add("tickets", allTicketsJson); - for (final Iterator>>> iterator = this.tickets.entryIterator(); -+ for (final Iterator>>> iterator = this.tickets.entryIterator(); ++ for (final Iterator>>> iterator = this.tickets.entryIterator(); // Leaf - Replace ConcurrentLong2ReferenceChainedHashTable with custom map iterator.hasNext();) { - final ConcurrentLong2ReferenceChainedHashTable.TableEntry>> coordinateTickets = iterator.next(); -+ final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable.TableEntry>> coordinateTickets = iterator.next(); ++ final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable.TableEntry>> coordinateTickets = iterator.next(); // Leaf - Replace ConcurrentLong2ReferenceChainedHashTable with custom map final long coordinate = coordinateTickets.getKey(); final SortedArraySet> tickets = coordinateTickets.getValue(); diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java -index 310a8f80debadd64c2d962ebf83b7d0505ce6e42..01598347ea545f2ff2ac337086345d7369a64520 100644 +index 310a8f80debadd64c2d962ebf83b7d0505ce6e42..3a7fad46465cac8d2c1b0933b457f5b075586709 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java @@ -35,11 +35,11 @@ public abstract class ThreadedTicketLevelPropagator { @@ -76,17 +73,17 @@ index 310a8f80debadd64c2d962ebf83b7d0505ce6e42..01598347ea545f2ff2ac337086345d73 private final UpdateQueue updateQueue; - private final ConcurrentLong2ReferenceChainedHashTable
sections; -+ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable
sections; ++ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable
sections; // Leaf - Replace ConcurrentLong2ReferenceChainedHashTable with custom map public ThreadedTicketLevelPropagator() { this.updateQueue = new UpdateQueue(); - this.sections = new ConcurrentLong2ReferenceChainedHashTable<>(); -+ this.sections = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>(); ++ this.sections = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>(); // Leaf - Replace ConcurrentLong2ReferenceChainedHashTable with custom map } // must hold ticket lock for: diff --git a/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java b/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java -index 1487b7d8be435b3fbad2aabd05796965b4775a87..a1459e3bc2076575e3f52d9e2b7d49630afeb799 100644 +index 1487b7d8be435b3fbad2aabd05796965b4775a87..57a702bafb36858979427809f8ae4fc1b001c8d6 100644 --- a/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java +++ b/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java @@ -740,7 +740,7 @@ public final class StarLightInterface { @@ -94,12 +91,12 @@ index 1487b7d8be435b3fbad2aabd05796965b4775a87..a1459e3bc2076575e3f52d9e2b7d4963 public static final class ServerLightQueue extends LightQueue { - private final ConcurrentLong2ReferenceChainedHashTable chunkTasks = new ConcurrentLong2ReferenceChainedHashTable<>(); -+ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable chunkTasks = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>(); ++ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable chunkTasks = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>(); // Leaf - Replace ConcurrentLong2ReferenceChainedHashTable with custom map public ServerLightQueue(final StarLightInterface lightInterface) { super(lightInterface); diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index b1f1b596a597d559aa672a3cb46a03917ad746af..d75f85208da0c7424fc95ae0d8ebb0a725dda0a7 100644 +index b1f1b596a597d559aa672a3cb46a03917ad746af..0860a700106e8c1afe58c77150a0f3aee8393fdd 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -72,7 +72,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -107,7 +104,7 @@ index b1f1b596a597d559aa672a3cb46a03917ad746af..d75f85208da0c7424fc95ae0d8ebb0a7 private NaturalSpawner.SpawnState lastSpawnState; // Paper start - private final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>(); -+ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable fullChunks = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>(); ++ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable fullChunks = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>(); // Leaf - Replace ConcurrentLong2ReferenceChainedHashTable with custom map public int getFullChunksCount() { return this.fullChunks.size(); } diff --git a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch index 63d3116c..7d340622 100644 --- a/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0154-Async-target-finding.patch @@ -32,16 +32,18 @@ index 24926aa7ed5c78b235659daf18b224b14beb744c..98af1ad020a003db66d7319f33d43dee public String getLocalIp() { diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java -index 33dd16a26edd2974f04d9a868d3e58e8e3060032..0817ad4e16d8858b7d92e028d551e9184f357eb6 100644 +index 33dd16a26edd2974f04d9a868d3e58e8e3060032..eb0589b203bcf72cd24bb37f2c448c23cb8d6f2b 100644 --- a/net/minecraft/server/dedicated/DedicatedServer.java +++ b/net/minecraft/server/dedicated/DedicatedServer.java -@@ -255,6 +255,9 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface +@@ -255,6 +255,11 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface DedicatedServer.LOGGER.info("Using " + serverLevelTickingSemaphore.availablePermits() + " permits for parallel world ticking"); // SparklyPaper - parallel world ticking } // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) ++ // Leaf start - Async target finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { + asyncGoalThread = new org.dreeam.leaf.async.ai.AsyncGoalThread(this); + } ++ // Leaf end - Async target finding com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now // Gale start - Pufferfish - SIMD support @@ -134,80 +136,85 @@ index 90bdcd168ad5b1a940f81b191bd59a34d3a33070..fbfb35dad8b07c31f967d33fb04cfcfc // Paper start - log detailed entity tick information diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 90879616842cc61d15854b07f56f6fcb89f11074..f114a8f5143799d72e36e0a535888c5fb25213e1 100644 +index 0903508d2cd3c78602e62dbcff4aa70285bc4c4f..e99fd55a90c68cc96701e5291219d1d056ed266b 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -243,6 +243,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess protected Vec3 stuckSpeedMultiplier = Vec3.ZERO; @Nullable private Entity.RemovalReason removalReason; -+ private volatile boolean isRemoved = false; // Leaf - volatile removal check ++ private volatile boolean isRemoved = false; // Leaf - Async target finding - volatile removal check public static final float DEFAULT_BB_WIDTH = 0.6F; public static final float DEFAULT_BB_HEIGHT = 1.8F; public float moveDist; -@@ -5036,7 +5037,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -5035,7 +5036,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public final boolean isRemoved() { - return this.removalReason != null; -+ // Leaf start - volatile removal check ++ // Leaf start - Async target finding ++ // Volatile removal check + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { + return this.isRemoved; + } else { + return this.removalReason != null; + } -+ // Leaf end - volatile removal check ++ // Leaf end - Async target finding } @Nullable -@@ -5063,6 +5070,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -5062,6 +5070,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers if (this.removalReason == null) { this.removalReason = removalReason; -+ // Leaf start - volatile removal check ++ // Leaf start - Async target finding ++ // Volatile removal check + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled && this.removalReason != null) { + this.isRemoved = true; + } -+ // Leaf end - volatile removal check ++ // Leaf end - Async target finding } if (this.removalReason.shouldDestroy()) { -@@ -5082,6 +5094,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -5081,6 +5095,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess public void unsetRemoved() { this.removalReason = null; -+ // Leaf start - volatile removal check ++ // Leaf start - Async target finding ++ // Volatile removal check + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { + this.isRemoved = false; + } -+ // Leaf end - volatile removal check ++ // Leaf end - Async target finding } // Paper start - Folia schedulers diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java -index 05d5cde42b7011091ef4ee874c0d9d5586ae3f10..bf021604e0c345ef72cbc55d4f8fe996d2d561f7 100644 +index 05d5cde42b7011091ef4ee874c0d9d5586ae3f10..7b4330c15a2bdf63882c4ed025f2b426592a9bba 100644 --- a/net/minecraft/world/entity/Mob.java +++ b/net/minecraft/world/entity/Mob.java -@@ -144,6 +144,10 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab +@@ -144,6 +144,12 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab private float restrictRadius = -1.0F; public boolean aware = true; // CraftBukkit public int ticksSinceLastInteraction; // Purpur - Entity lifespan -+ public boolean tickingTarget; // Leaf - Async target finding ++ // Leaf start - Async target finding ++ public boolean tickingTarget; + public final org.dreeam.leaf.async.ai.Waker getGoalCtx() { + return tickingTarget ? this.targetSelector.ctx : this.goalSelector.ctx; + } ++ // Leaf end - Async target finding protected Mob(EntityType entityType, Level level) { super(entityType, level); -@@ -225,12 +229,21 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab +@@ -225,12 +231,21 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab } // Paper end - Skip AI during inactive ticks for non-aware mobs boolean isThrottled = org.dreeam.leaf.config.modules.opt.ThrottleInactiveGoalSelectorTick.enabled && _pufferfish_inactiveTickDisableCounter++ % 20 != 0; // Pufferfish - throttle inactive goal selector ticking -+ this.tickingTarget = false; // Leaf ++ this.tickingTarget = false; // Leaf - Async target finding if (this.goalSelector.inactiveTick(this.activatedPriority, true) && !isThrottled) { // Pufferfish - pass activated priroity // Pufferfish - throttle inactive goal selector ticking this.goalSelector.tick(); } -+ this.tickingTarget = true; // Leaf ++ this.tickingTarget = true; // Leaf - Async target finding if (this.targetSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priority this.targetSelector.tick(); } @@ -217,11 +224,11 @@ index 05d5cde42b7011091ef4ee874c0d9d5586ae3f10..bf021604e0c345ef72cbc55d4f8fe996 + ((ServerLevel) this.level()).asyncGoalExecutor.submit(this.getId()); + } + } -+ // Leaf start - Async target finding ++ // Leaf end - Async target finding } // Paper end -@@ -914,17 +927,28 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab +@@ -914,17 +929,28 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab // Paper end - Allow nerfed mobs to jump and float this.sensing.tick(); int i = this.tickCount + this.getId(); @@ -251,22 +258,13 @@ index 05d5cde42b7011091ef4ee874c0d9d5586ae3f10..bf021604e0c345ef72cbc55d4f8fe996 this.navigation.tick(); this.customServerAiStep((ServerLevel)this.level()); diff --git a/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java b/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java -index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..5239f4dfeaed8adccceb2a6d6578308261f46628 100644 +index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..776f18287b6b431b6b33d370b124825798cf103a 100644 --- a/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java +++ b/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java @@ -67,15 +67,24 @@ public class AvoidEntityGoal extends Goal { @Override public boolean canUse() { -- this.toAvoid = getServerLevel(this.mob) -- .getNearestEntity( -- this.mob.level().getEntitiesOfClass(this.avoidClass, this.mob.getBoundingBox().inflate(this.maxDist, 3.0, this.maxDist), livingEntity -> true), -- this.avoidEntityTargeting, -- this.mob, -- this.mob.getX(), -- this.mob.getY(), -- this.mob.getZ() -- ); + // Leaf start - Async Avoid Entity Finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + if (!poll()) { @@ -274,32 +272,31 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..5239f4dfeaed8adccceb2a6d65783082 + return false; + } + } else { -+ this.toAvoid = getServerLevel(this.mob) -+ .getNearestEntity( -+ this.mob.level().getEntitiesOfClass(this.avoidClass, this.mob.getBoundingBox().inflate(this.maxDist, 3.0, this.maxDist), livingEntity -> true), -+ this.avoidEntityTargeting, -+ this.mob, -+ this.mob.getX(), -+ this.mob.getEyeY(), -+ this.mob.getZ() -+ ); + this.toAvoid = getServerLevel(this.mob) + .getNearestEntity( + this.mob.level().getEntitiesOfClass(this.avoidClass, this.mob.getBoundingBox().inflate(this.maxDist, 3.0, this.maxDist), livingEntity -> true), + this.avoidEntityTargeting, + this.mob, + this.mob.getX(), +- this.mob.getY(), ++ this.mob.getEyeY(), // Leaf - Async Avoid Entity Finding + this.mob.getZ() + ); + } + // Leaf end - Async Avoid Entity Finding if (this.toAvoid == null) { return false; } else { -@@ -91,6 +100,47 @@ public class AvoidEntityGoal extends Goal { +@@ -91,6 +100,45 @@ public class AvoidEntityGoal extends Goal { } } + // Leaf start - Async Avoid Entity Finding + private boolean poll() { -+ if (!(this.mob.getGoalCtx().result() instanceof LivingEntity entity)) { return false; } ++ if (!(this.mob.getGoalCtx().result() instanceof LivingEntity target)) return false; + var serverLevel = getServerLevel(this.mob); -+ if (serverLevel == null || !entity.isAlive() || !this.avoidEntityTargeting.test(serverLevel, this.mob, entity)) { -+ return false; -+ } -+ this.toAvoid = (T) entity; ++ if (serverLevel == null || !target.isAlive() || !this.avoidEntityTargeting.test(serverLevel, this.mob, target)) return false; ++ this.toAvoid = (T) target; + return true; + } + @@ -337,24 +334,20 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..5239f4dfeaed8adccceb2a6d65783082 public boolean canContinueToUse() { return !this.pathNav.isDone(); diff --git a/net/minecraft/world/entity/ai/goal/BegGoal.java b/net/minecraft/world/entity/ai/goal/BegGoal.java -index 28ef40e8a645989ea181297069cf2bbe571f3082..5f1a0ccaed29324a24bfbea96c9dc3adfeb11ad5 100644 +index 28ef40e8a645989ea181297069cf2bbe571f3082..4db188a08c1d13f77fb268ba03ac5d7fb9b71ad0 100644 --- a/net/minecraft/world/entity/ai/goal/BegGoal.java +++ b/net/minecraft/world/entity/ai/goal/BegGoal.java -@@ -27,8 +27,54 @@ public class BegGoal extends Goal { +@@ -27,8 +27,50 @@ public class BegGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.LOOK)); } + // Leaf start - Async Target Finding + protected boolean poll() { -+ if (!(this.wolf.getGoalCtx().result() instanceof Player t)) { return false; } -+ if (t == null) { -+ return false; -+ } ++ if (!(this.wolf.getGoalCtx().result() instanceof Player target)) return false; ++ if (target == null) return false; + ServerLevel serverLevel = getServerLevel(this.wolf); -+ if (serverLevel == null || !t.isAlive() || !playerHoldingInteresting(t)) { -+ return false; -+ } -+ this.player = t; ++ if (serverLevel == null || !target.isAlive() || !playerHoldingInteresting(target)) return false; ++ this.player = target; + return true; + } + @@ -395,35 +388,29 @@ index 28ef40e8a645989ea181297069cf2bbe571f3082..5f1a0ccaed29324a24bfbea96c9dc3ad this.player = this.level.getNearestPlayer(this.begTargeting, this.wolf); return this.player != null && this.playerHoldingInteresting(this.player); } -@@ -59,14 +105,16 @@ public class BegGoal extends Goal { +@@ -59,10 +101,10 @@ public class BegGoal extends Goal { this.lookTime--; } - private boolean playerHoldingInteresting(Player player) { -+ // Leaf start - static -+ private static boolean playerHoldingInteresting(Player player) { ++ private static boolean playerHoldingInteresting(Player player) { // Leaf start - Async Target Finding - static for (InteractionHand interactionHand : InteractionHand.values()) { ItemStack itemInHand = player.getItemInHand(interactionHand); - if (itemInHand.is(Items.BONE) || this.wolf.isFood(itemInHand)) { -+ if (itemInHand.is(Items.BONE) || itemInHand.is(net.minecraft.tags.ItemTags.WOLF_FOOD)) { ++ if (itemInHand.is(Items.BONE) || itemInHand.is(net.minecraft.tags.ItemTags.WOLF_FOOD)) { // Leaf end - Async Target Finding return true; } } - - return false; - } -+ // Leaf end - static - } diff --git a/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java b/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java -index 4c46cd105cde3cbcde65a02ff691c3a8edd56c76..d26bebdc66bf30a30fba5c1ba70e71479aff68a1 100644 +index 4c46cd105cde3cbcde65a02ff691c3a8edd56c76..b3205c9f35687bc37124876198ec2d657ccaa96c 100644 --- a/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java +++ b/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java -@@ -52,6 +52,14 @@ public class CatLieOnBedGoal extends MoveToBlockGoal { +@@ -52,6 +52,13 @@ public class CatLieOnBedGoal extends MoveToBlockGoal { @Override protected boolean isValidTarget(LevelReader level, BlockPos pos) { -+ // Leaf - Async search block - return level.isEmptyBlock(pos.above()) && level.getBlockState(pos).is(BlockTags.BEDS); +- return level.isEmptyBlock(pos.above()) && level.getBlockState(pos).is(BlockTags.BEDS); ++ return level.isEmptyBlock(pos.above()) && level.getBlockState(pos).is(BlockTags.BEDS); // Leaf - Async search block - diff on change } + + // Leaf start - Async search block @@ -434,22 +421,22 @@ index 4c46cd105cde3cbcde65a02ff691c3a8edd56c76..d26bebdc66bf30a30fba5c1ba70e7147 + // Leaf end - Async search block } diff --git a/net/minecraft/world/entity/ai/goal/CatSitOnBlockGoal.java b/net/minecraft/world/entity/ai/goal/CatSitOnBlockGoal.java -index 9954f49bc364969c7ccb37f4186fa2ab8710f6ae..b0a93215a7d5e8fe58331f261d7b4f87852cd4b2 100644 +index 9954f49bc364969c7ccb37f4186fa2ab8710f6ae..057090e3134048e75dbaefb703e8f2d35a172a3b 100644 --- a/net/minecraft/world/entity/ai/goal/CatSitOnBlockGoal.java +++ b/net/minecraft/world/entity/ai/goal/CatSitOnBlockGoal.java -@@ -44,9 +44,11 @@ public class CatSitOnBlockGoal extends MoveToBlockGoal { +@@ -44,14 +44,21 @@ public class CatSitOnBlockGoal extends MoveToBlockGoal { @Override protected boolean isValidTarget(LevelReader level, BlockPos pos) { -+ // Leaf - Async search block - if (!level.isEmptyBlock(pos.above())) { +- if (!level.isEmptyBlock(pos.above())) { ++ if (!level.isEmptyBlock(pos.above())) { // Leaf - Async search block - diff on change return false; } else { -+ // Leaf - Async search block - BlockState blockState = level.getBlockState(pos); +- BlockState blockState = level.getBlockState(pos); ++ BlockState blockState = level.getBlockState(pos); // Leaf - Async search block - diff on change return blockState.is(Blocks.CHEST) ? ChestBlockEntity.getOpenCount(level, pos) < 1 -@@ -54,4 +56,11 @@ public class CatSitOnBlockGoal extends MoveToBlockGoal { + : blockState.is(Blocks.FURNACE) && blockState.getValue(FurnaceBlock.LIT) || blockState.is(BlockTags.BEDS, state -> state.getOptionalValue(BedBlock.PART).map(bedPart -> bedPart != BedPart.HEAD).orElse(true)); } } @@ -462,7 +449,7 @@ index 9954f49bc364969c7ccb37f4186fa2ab8710f6ae..b0a93215a7d5e8fe58331f261d7b4f87 + // Leaf end - Async search block } diff --git a/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java b/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java -index 1fe4d1877ed52017d4d22ddb9b77f78e2b93dff9..ec52e6c8110876eb915485c54a8102c4afe460f6 100644 +index 1fe4d1877ed52017d4d22ddb9b77f78e2b93dff9..2d2ffa9e89f4954e096c48cba96db995a92433e5 100644 --- a/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java +++ b/net/minecraft/world/entity/ai/goal/FollowBoatGoal.java @@ -23,8 +23,54 @@ public class FollowBoatGoal extends Goal { @@ -471,9 +458,9 @@ index 1fe4d1877ed52017d4d22ddb9b77f78e2b93dff9..ec52e6c8110876eb915485c54a8102c4 + // Leaf start - Async Target Finding + private boolean poll() { -+ if (!(this.mob.getGoalCtx().result() instanceof Player t)) { return false; } ++ if (!(this.mob.getGoalCtx().result() instanceof Player target)) return false; + var serverLevel = getServerLevel(this.mob); -+ return serverLevel != null && t.isAlive() && !t.isSpectator() && (Mth.abs(t.xxa) > 0.0F || Mth.abs(t.zza) > 0.0F); ++ return serverLevel != null && target.isAlive() && !target.isSpectator() && (Mth.abs(target.xxa) > 0.0F || Mth.abs(target.zza) > 0.0F); + } + + private void findTargetAsync() { @@ -525,12 +512,12 @@ index 1fe4d1877ed52017d4d22ddb9b77f78e2b93dff9..ec52e6c8110876eb915485c54a8102c4 } - return this.following != null && (Mth.abs(this.following.xxa) > 0.0F || Mth.abs(this.following.zza) > 0.0F) || flag; -+ return flag; // Leaf - move above ++ return flag; // Leaf - Async Target Finding - move above } @Override diff --git a/net/minecraft/world/entity/ai/goal/FollowMobGoal.java b/net/minecraft/world/entity/ai/goal/FollowMobGoal.java -index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..0d123e6bac5748b6c5efd76f4c1e086cbf749783 100644 +index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..911ceddb4ded974a02355c76e5d4133632ae4487 100644 --- a/net/minecraft/world/entity/ai/goal/FollowMobGoal.java +++ b/net/minecraft/world/entity/ai/goal/FollowMobGoal.java @@ -38,6 +38,15 @@ public class FollowMobGoal extends Goal { @@ -549,18 +536,16 @@ index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..0d123e6bac5748b6c5efd76f4c1e086c List entitiesOfClass = this.mob.level().getEntitiesOfClass(Mob.class, this.mob.getBoundingBox().inflate(this.areaSize), this.followPredicate); if (!entitiesOfClass.isEmpty()) { for (Mob mob : entitiesOfClass) { -@@ -51,6 +60,45 @@ public class FollowMobGoal extends Goal { +@@ -51,6 +60,43 @@ public class FollowMobGoal extends Goal { return false; } + // Leaf start - Async Follow Mob Finding + private boolean poll() { -+ if (!(this.mob.getGoalCtx().result() instanceof Mob t)) { return false; } ++ if (!(this.mob.getGoalCtx().result() instanceof Mob target)) return false; + var serverLevel = getServerLevel(this.mob); -+ if (serverLevel == null || !t.isAlive() || t.isInvisible()) { -+ return false; -+ } -+ this.followingMob = t; ++ if (serverLevel == null || !target.isAlive() || target.isInvisible()) return false; ++ this.followingMob = target; + return true; + } + @@ -596,20 +581,18 @@ index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..0d123e6bac5748b6c5efd76f4c1e086c public boolean canContinueToUse() { return this.followingMob != null && !this.navigation.isDone() && this.mob.distanceToSqr(this.followingMob) > this.stopDistance * this.stopDistance; diff --git a/net/minecraft/world/entity/ai/goal/FollowParentGoal.java b/net/minecraft/world/entity/ai/goal/FollowParentGoal.java -index 3093f03d4f298bf39fec8bad2b6c22518774aea8..ee42674761653cacc5045fcb60e314fbe25f390f 100644 +index 3093f03d4f298bf39fec8bad2b6c22518774aea8..80f9de7b2c03c1477dae0f42b328e0100f78e58b 100644 --- a/net/minecraft/world/entity/ai/goal/FollowParentGoal.java +++ b/net/minecraft/world/entity/ai/goal/FollowParentGoal.java -@@ -19,11 +19,68 @@ public class FollowParentGoal extends Goal { +@@ -19,11 +19,66 @@ public class FollowParentGoal extends Goal { this.speedModifier = speedModifier; } + // Leaf start - Async Target Finding + protected boolean poll() { -+ if (!(this.animal.getGoalCtx().result() instanceof Animal t)) { return false; } ++ if (!(this.animal.getGoalCtx().result() instanceof Animal target)) return false; + var serverLevel = getServerLevel(animal); -+ if (serverLevel == null || !t.isAlive() || animal.distanceToSqr(t) < 9.0) { -+ return false; -+ } ++ if (serverLevel == null || !target.isAlive() || animal.distanceToSqr(target) < 9.0) return false; + this.parent = animal; + return true; + } @@ -668,16 +651,16 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..ee42674761653cacc5045fcb60e314fb List entitiesOfClass = this.animal .level() .getEntitiesOfClass((Class)this.animal.getClass(), this.animal.getBoundingBox().inflate(8.0, 4.0, 8.0)); -@@ -43,6 +100,7 @@ public class FollowParentGoal extends Goal { +@@ -43,6 +98,7 @@ public class FollowParentGoal extends Goal { if (animal == null) { return false; } else if (d < 9.0) { -+ // Leaf - Async Target Finding ++ // Leaf - Async Target Finding - diff on change return false; } else { this.parent = animal; diff --git a/net/minecraft/world/entity/ai/goal/GoalSelector.java b/net/minecraft/world/entity/ai/goal/GoalSelector.java -index e82e32407cec6109b9c3b0106295217f4a3f4aa2..32463fae3d3a58e71c4aae2d81b2a1b60fee7d69 100644 +index e82e32407cec6109b9c3b0106295217f4a3f4aa2..ec56b4e8acd2dad23a94b1fe9145e6256da54493 100644 --- a/net/minecraft/world/entity/ai/goal/GoalSelector.java +++ b/net/minecraft/world/entity/ai/goal/GoalSelector.java @@ -26,6 +26,13 @@ public class GoalSelector { @@ -694,10 +677,11 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..32463fae3d3a58e71c4aae2d81b2a1b6 public void addGoal(int priority, Goal goal) { this.availableGoals.add(new WrappedGoal(priority, goal)); } -@@ -85,7 +92,107 @@ public class GoalSelector { +@@ -85,7 +92,111 @@ public class GoalSelector { return true; } ++ // Leaf start - Async target finding + public final boolean poll() { + if (this.ctxGoals == null || ctx.wake != null) { + return false; @@ -790,42 +774,35 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..32463fae3d3a58e71c4aae2d81b2a1b6 + } + ctx.wake = null; + } ++ // Leaf end - Async target finding + public void tick() { ++ // Leaf start - Async target finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { + if (this.ctxGoals == null) { + this.ctxGoals = this.availableGoals.toArray(new WrappedGoal[0]); + } + return; + } ++ // Leaf end - Async target finding + for (WrappedGoal wrappedGoal : this.availableGoals) { if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.goalTypes) || !wrappedGoal.canContinueToUse())) { // Paper - Perf: optimize goal types by removing streams wrappedGoal.stop(); -@@ -94,6 +201,7 @@ public class GoalSelector { - - this.lockedFlags.entrySet().removeIf(entry -> !entry.getValue().isRunning()); - -+ - for (WrappedGoal wrappedGoalx : this.availableGoals) { - // Paper start - if (!wrappedGoalx.isRunning() && !goalContainsAnyFlags(wrappedGoalx, this.goalTypes) && goalCanBeReplacedForAllFlags(wrappedGoalx, this.lockedFlags) && wrappedGoalx.canUse()) { diff --git a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java -index be59d0c27a83b329ec3f97c029cfb9c114e22472..239567f1ac82e17ec8f0624f1a33c1faa7ad13fe 100644 +index be59d0c27a83b329ec3f97c029cfb9c114e22472..7c54964a5329e12a9a4a1fdb2417ccbd3f0bfae2 100644 --- a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java +++ b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java -@@ -20,19 +20,94 @@ public class LlamaFollowCaravanGoal extends Goal { +@@ -20,20 +20,93 @@ public class LlamaFollowCaravanGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.MOVE)); } + // Leaf start - Async Target Finding + private @javax.annotation.Nullable Llama poll() { -+ if (!(this.llama.getGoalCtx().result() instanceof Llama t)) { return null; } ++ if (!(this.llama.getGoalCtx().result() instanceof Llama target)) return null; + var serverLevel = getServerLevel(this.llama); -+ if (serverLevel == null || !t.isAlive()) { -+ return null; -+ } -+ return t; ++ if (serverLevel == null || !target.isAlive()) return null; ++ return target; + } + + private void findTargetAsync() { @@ -849,7 +826,7 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..239567f1ac82e17ec8f0624f1a33c1fa + double d = Double.MAX_VALUE; + + for (Entity entity : entities) { -+ Llama llama1 = (Llama)entity; ++ Llama llama1 = (Llama) entity; + if (llama1.inCaravan() && !llama1.hasCaravanTail()) { + double d1 = llama1.distanceToSqr(pos); + if (!(d1 > d)) { @@ -861,7 +838,7 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..239567f1ac82e17ec8f0624f1a33c1fa + + if (target == null) { + for (Entity entityx : entities) { -+ Llama llama1 = (Llama)entityx; ++ Llama llama1 = (Llama) entityx; + if (llama1.isLeashed() && !llama1.hasCaravanTail()) { + double d1 = llama1.distanceToSqr(pos); + if (!(d1 > d)) { @@ -880,6 +857,7 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..239567f1ac82e17ec8f0624f1a33c1fa + }; + } + // Leaf end - Async Target Finding ++ @Override public boolean canUse() { if (!this.llama.level().purpurConfig.llamaJoinCaravans || !this.llama.shouldJoinCaravan) return false; // Purpur - Llama API // Purpur - Config to disable Llama caravans @@ -902,36 +880,38 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..239567f1ac82e17ec8f0624f1a33c1fa }); - Llama llama = null; - double d = Double.MAX_VALUE; -+ // Llama llama = null; // Leaf -+ // double d = Double.MAX_VALUE; // Leaf ++ // Llama llama = null; // Leaf - Async Target Finding ++ // double d = Double.MAX_VALUE; // Leaf - Async Target Finding for (Entity entity : entities) { Llama llama1 = (Llama)entity; -+ // Leaf - Async Target Finding - if (llama1.inCaravan() && !llama1.hasCaravanTail()) { +- if (llama1.inCaravan() && !llama1.hasCaravanTail()) { ++ if (llama1.inCaravan() && !llama1.hasCaravanTail()) { // Leaf - Async Target Finding - diff on change double d1 = this.llama.distanceToSqr(llama1); if (!(d1 > d)) { -@@ -45,6 +120,7 @@ public class LlamaFollowCaravanGoal extends Goal { + d = d1; +@@ -45,7 +118,7 @@ public class LlamaFollowCaravanGoal extends Goal { if (llama == null) { for (Entity entityx : entities) { Llama llama1 = (Llama)entityx; -+ // Leaf - Async Target Finding - if (llama1.isLeashed() && !llama1.hasCaravanTail()) { +- if (llama1.isLeashed() && !llama1.hasCaravanTail()) { ++ if (llama1.isLeashed() && !llama1.hasCaravanTail()) { // Leaf - Async Target Finding - diff on change double d1 = this.llama.distanceToSqr(llama1); if (!(d1 > d)) { -@@ -54,6 +130,7 @@ public class LlamaFollowCaravanGoal extends Goal { + d = d1; +@@ -54,6 +127,7 @@ public class LlamaFollowCaravanGoal extends Goal { } } } -+ } // Leaf ++ } // Leaf - Async Target Finding if (llama == null) { return false; diff --git a/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java b/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java -index 6463c3c9b08d6058f2843c225b08a40fc30a960b..819f7947b0c9c546cf45b2effe72cfb597a99ce6 100644 +index 6463c3c9b08d6058f2843c225b08a40fc30a960b..0afad8ddb0e9d083d47b96627d999f0ee28b9446 100644 --- a/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java +++ b/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java -@@ -48,32 +48,94 @@ public class LookAtPlayerGoal extends Goal { +@@ -48,32 +48,87 @@ public class LookAtPlayerGoal extends Goal { @Override public boolean canUse() { @@ -939,19 +919,16 @@ index 6463c3c9b08d6058f2843c225b08a40fc30a960b..819f7947b0c9c546cf45b2effe72cfb5 + if (poll()) { + return true; + } -+ if (this.mob.getRandom().nextFloat() >= this.probability) { return false; + } + if (this.mob.getTarget() != null) { + this.lookAt = this.mob.getTarget(); + } -+ + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + getLookAsync(); + return false; + } -+ + ServerLevel serverLevel = getServerLevel(this.mob); + if (this.lookAtType == Player.class) { + this.lookAt = serverLevel.getNearestPlayer(this.lookAtContext, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); @@ -987,22 +964,18 @@ index 6463c3c9b08d6058f2843c225b08a40fc30a960b..819f7947b0c9c546cf45b2effe72cfb5 - ); - } + return this.lookAt != null; -+ // Leaf end - Async look finding + } - return this.lookAt != null; -+ // Leaf start - Async look finding + protected boolean poll() { -+ if (!(this.mob.getGoalCtx().result() instanceof LivingEntity t)) { return false; } ++ if (!(this.mob.getGoalCtx().result() instanceof LivingEntity target)) return false; + if (this.mob.getTarget() != null) { + this.lookAt = this.mob.getTarget(); + return true; -+ } -+ ServerLevel serverLevel = getServerLevel(this.mob); -+ if (!t.isAlive() || !this.lookAtContext.test(serverLevel, this.mob, t)) { -+ return false; } -+ this.lookAt = t; ++ ServerLevel serverLevel = getServerLevel(this.mob); ++ if (!target.isAlive() || !this.lookAtContext.test(serverLevel, this.mob, target)) return false; ++ this.lookAt = target; + return true; + } + @@ -1046,7 +1019,7 @@ index 6463c3c9b08d6058f2843c225b08a40fc30a960b..819f7947b0c9c546cf45b2effe72cfb5 @Override public boolean canContinueToUse() { diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..99e6e7bac2b5af1b1abb1de6b51aed1f44e5e8b0 100644 +index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..292749c786b85f86fa5f65c49fa83539fd0603b4 100644 --- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java @@ -41,8 +41,69 @@ public abstract class MoveToBlockGoal extends Goal { @@ -1055,7 +1028,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..99e6e7bac2b5af1b1abb1de6b51aed1f + // Leaf start - Async search block + protected boolean poll() { -+ if (!(this.mob.getGoalCtx().result() instanceof BlockPos blockPos1)) { return false; } ++ if (!(this.mob.getGoalCtx().result() instanceof BlockPos blockPos1)) return false; + if (!this.mob.level().hasChunkAt(blockPos1) + || !this.mob.isWithinRestriction(blockPos1) + || !this.isValidTarget(this.mob.level(), blockPos1)) { @@ -1119,21 +1092,24 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..99e6e7bac2b5af1b1abb1de6b51aed1f if (this.nextStartTick > 0) { this.nextStartTick--; return false; -@@ -109,6 +170,10 @@ public abstract class MoveToBlockGoal extends Goal { +@@ -109,6 +170,12 @@ public abstract class MoveToBlockGoal extends Goal { } protected boolean findNearestBlock() { ++ // Leaf start - Async search block + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchBlock) { + getBlockAsync(); + return false; + } ++ // Leaf end - Async search block int i = this.searchRange; int i1 = this.verticalSearchRange; BlockPos blockPos = this.mob.blockPosition(); -@@ -133,5 +198,104 @@ public abstract class MoveToBlockGoal extends Goal { +@@ -133,5 +200,108 @@ public abstract class MoveToBlockGoal extends Goal { return false; } ++ // Leaf start - Async search block + protected static boolean findNearestBlockAsync( + final org.dreeam.leaf.async.ai.Waker ctx, + final TypeToCheck ty, @@ -1170,19 +1146,21 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..99e6e7bac2b5af1b1abb1de6b51aed1f + private static boolean isWithinRestriction(float restrictRadius, BlockPos restrictCenter, BlockPos pos) { + return restrictRadius == -1.0F || restrictCenter.distSqr(pos) < restrictRadius * restrictRadius; + } ++ // Leaf end - Async search block + protected abstract boolean isValidTarget(LevelReader level, BlockPos pos); + ++ // Leaf start - Async search block + protected abstract TypeToCheck typeToCheck(); + + private static boolean isValidTargetAsync( -+ TypeToCheck ty, ++ TypeToCheck type, + @org.jetbrains.annotations.Nullable net.minecraft.world.level.block.Block blockToRemoveTy, + PathfinderMob mob, + LevelReader level, + BlockPos pos + ) { -+ switch (ty) { ++ switch (type) { + case CatLie -> { + return level.isEmptyBlock(pos.above()) && level.getBlockState(pos).is(net.minecraft.tags.BlockTags.BEDS); + } @@ -1233,10 +1211,11 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..99e6e7bac2b5af1b1abb1de6b51aed1f + } + case null -> throw new IllegalStateException(); + } ++ // Leaf end - Async search block + } } diff --git a/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java b/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java -index 3c274d917bca9de87abfb842f5f332e112a7a2d7..aa743d8f4ec0c1d78e5e58a7b5e36bca08b8b2ef 100644 +index 3c274d917bca9de87abfb842f5f332e112a7a2d7..e1d185b2f5e7eb7a00501ff85b4e16d3540066f4 100644 --- a/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java +++ b/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java @@ -19,10 +19,20 @@ public class OfferFlowerGoal extends Goal { @@ -1261,19 +1240,17 @@ index 3c274d917bca9de87abfb842f5f332e112a7a2d7..aa743d8f4ec0c1d78e5e58a7b5e36bca } else { this.villager = getServerLevel(this.golem) .getNearestEntity( -@@ -38,6 +48,47 @@ public class OfferFlowerGoal extends Goal { +@@ -38,6 +48,45 @@ public class OfferFlowerGoal extends Goal { } } + + // Leaf start - Async look finding + protected boolean poll() { -+ if (!(this.golem.getGoalCtx().result() instanceof Villager t)) { return false; } ++ if (!(this.golem.getGoalCtx().result() instanceof Villager target)) return false; + var serverLevel = getServerLevel(this.golem); -+ if (!t.isAlive() || !OFFER_TARGER_CONTEXT.test(serverLevel, this.golem, t)) { -+ return false; -+ } -+ this.villager = t; ++ if (!target.isAlive() || !OFFER_TARGER_CONTEXT.test(serverLevel, this.golem, target)) return false; ++ this.villager = target; + return true; + } + @@ -1310,18 +1287,9 @@ index 3c274d917bca9de87abfb842f5f332e112a7a2d7..aa743d8f4ec0c1d78e5e58a7b5e36bca public boolean canContinueToUse() { return this.tick > 0; diff --git a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java -index 95fa516910a3834bbd4db6d11279e13a1f0dac41..f7ddae601abbe9e22a35c7cb4f9763e61fa7ff81 100644 +index c67a88c9c77ece7c85ffb169ac96da4f28291228..14d9b492ba431d534e0c6a567d0b7700b4c8a02d 100644 --- a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java +++ b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java -@@ -22,7 +22,7 @@ import net.minecraft.world.level.chunk.status.ChunkStatus; - import net.minecraft.world.phys.Vec3; - - public class RemoveBlockGoal extends MoveToBlockGoal { -- private final Block blockToRemove; -+ protected final Block blockToRemove; // Leaf - Async search block - private final Mob removerMob; - private int ticksSinceReachedGoal; - private static final int WAIT_AFTER_BLOCK_FOUND = 20; @@ -37,7 +37,14 @@ public class RemoveBlockGoal extends MoveToBlockGoal { public boolean canUse() { if (!getServerLevel(this.removerMob).purpurConfig.zombieBypassMobGriefing == !getServerLevel(this.removerMob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected @@ -1338,14 +1306,12 @@ index 95fa516910a3834bbd4db6d11279e13a1f0dac41..f7ddae601abbe9e22a35c7cb4f9763e6 this.nextStartTick--; return false; } else if (this.findNearestBlock()) { -@@ -149,10 +156,18 @@ public class RemoveBlockGoal extends MoveToBlockGoal { - - @Override +@@ -151,8 +158,15 @@ public class RemoveBlockGoal extends MoveToBlockGoal { protected boolean isValidTarget(LevelReader level, BlockPos pos) { -+ // Leaf - Async search block ChunkAccess chunk = level.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4); // Paper - Prevent AI rules from loading chunks return chunk != null - && chunk.getBlockState(pos).is(this.blockToRemove) +- && chunk.getBlockState(pos).is(this.blockToRemove) ++ && chunk.getBlockState(pos).is(this.blockToRemove) // Leaf - Async search block - diff on change && chunk.getBlockState(pos.above()).isAir() && chunk.getBlockState(pos.above(2)).isAir(); } @@ -1358,21 +1324,19 @@ index 95fa516910a3834bbd4db6d11279e13a1f0dac41..f7ddae601abbe9e22a35c7cb4f9763e6 + // Leaf end - Async search block } diff --git a/net/minecraft/world/entity/ai/goal/TemptGoal.java b/net/minecraft/world/entity/ai/goal/TemptGoal.java -index f88f618d34fb343b31de3af1a875d6633703df71..37aa00347e9cf391e447cff775e4493b36e05beb 100644 +index f88f618d34fb343b31de3af1a875d6633703df71..e42f77fc6d04a900fd8adb4682678f9218561aa8 100644 --- a/net/minecraft/world/entity/ai/goal/TemptGoal.java +++ b/net/minecraft/world/entity/ai/goal/TemptGoal.java -@@ -36,12 +36,62 @@ public class TemptGoal extends Goal { +@@ -36,12 +36,60 @@ public class TemptGoal extends Goal { this.targetingConditions = TEMPT_TARGETING.copy().selector((entity, level) -> this.shouldFollow(entity)); } + // Leaf start - Async Tempt Finding + private boolean poll() { -+ if (!(this.mob.getGoalCtx().result() instanceof Player t)) { return false; } ++ if (!(this.mob.getGoalCtx().result() instanceof Player target)) return false; + var serverLevel = getServerLevel(this.mob); -+ if (!t.isAlive() || !this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)).test(serverLevel, this.mob, t)) { -+ return false; -+ } -+ this.player = t; ++ if (!target.isAlive() || !this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)).test(serverLevel, this.mob, target)) return false; ++ this.player = target; + return true; + } + @@ -1425,7 +1389,7 @@ index f88f618d34fb343b31de3af1a875d6633703df71..37aa00347e9cf391e447cff775e4493b .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); // CraftBukkit start diff --git a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java -index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..921418da5f026edad4c78ba51127bb758c34bb9a 100644 +index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..941131fd448216afe2bf46dc6aab1d4b95c586fc 100644 --- a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java @@ -16,7 +16,7 @@ public class DefendVillageTargetGoal extends TargetGoal { @@ -1433,26 +1397,21 @@ index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..921418da5f026edad4c78ba51127bb75 @Nullable private LivingEntity potentialTarget; - private final TargetingConditions attackTargeting = TargetingConditions.forCombat().range(64.0); -+ private static final TargetingConditions attackTargeting = TargetingConditions.forCombat().range(64.0); // Leaf - static ++ private static final TargetingConditions attackTargeting = TargetingConditions.forCombat().range(64.0); // Leaf - Async Target Finding - static public DefendVillageTargetGoal(IronGolem golem) { super(golem, false, true); -@@ -24,8 +24,63 @@ public class DefendVillageTargetGoal extends TargetGoal { +@@ -24,8 +24,58 @@ public class DefendVillageTargetGoal extends TargetGoal { this.setFlags(EnumSet.of(Goal.Flag.TARGET)); } + // Leaf start - Async Target Finding + protected boolean poll() { -+ if (!(this.mob.getGoalCtx().result() instanceof LivingEntity t)) { return false; } ++ if (!(this.mob.getGoalCtx().result() instanceof LivingEntity target)) return false; + ServerLevel serverLevel = getServerLevel(this.mob); -+ if (serverLevel == null || !t.isAlive() || !attackTargeting.test(serverLevel, golem, t)) { -+ return false; -+ } -+ if ((t instanceof Player player && (player.isSpectator() || player.isCreative()))) { -+ return false; -+ } -+ this.potentialTarget = t; -+ ++ if (serverLevel == null || !target.isAlive() || !attackTargeting.test(serverLevel, golem, target)) return false; ++ if ((target instanceof Player player && (player.isSpectator() || player.isCreative()))) return false; ++ this.potentialTarget = target; + return true; + } + @@ -1501,16 +1460,17 @@ index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..921418da5f026edad4c78ba51127bb75 AABB aabb = this.golem.getBoundingBox().inflate(10.0, 8.0, 10.0); ServerLevel serverLevel = getServerLevel(this.golem); List nearbyEntities = serverLevel.getNearbyEntities(Villager.class, this.attackTargeting, this.golem, aabb); -@@ -42,6 +97,7 @@ public class DefendVillageTargetGoal extends TargetGoal { - } +@@ -43,7 +93,7 @@ public class DefendVillageTargetGoal extends TargetGoal { } -+ // Leaf - Async target finding return this.potentialTarget != null - && (!(this.potentialTarget instanceof Player) || !this.potentialTarget.isSpectator() && !((Player)this.potentialTarget).isCreative()); +- && (!(this.potentialTarget instanceof Player) || !this.potentialTarget.isSpectator() && !((Player)this.potentialTarget).isCreative()); ++ && (!(this.potentialTarget instanceof Player) || !this.potentialTarget.isSpectator() && !((Player)this.potentialTarget).isCreative()); // Leaf - Async target finding - diff on change } + + @Override diff --git a/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java -index 25fe78116ce01eeefe5c958423734195d27302eb..e19f2cb98c9669deb3a19114264668fbb29d3cc5 100644 +index 25fe78116ce01eeefe5c958423734195d27302eb..6998e7a102b711152d78f4dc590812168dd24cd6 100644 --- a/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java @@ -73,6 +73,56 @@ public class HurtByTargetGoal extends TargetGoal { @@ -1518,7 +1478,7 @@ index 25fe78116ce01eeefe5c958423734195d27302eb..e19f2cb98c9669deb3a19114264668fb double followDistance = this.getFollowDistance(); AABB aabb = AABB.unitCubeFromLowerCorner(this.mob.position()).inflate(followDistance, 10.0, followDistance); + -+ // Leaf start - async alert other ++ // Leaf start - Async alert other + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.alertOther) { + final var self = this.mob; + final var ctx = self.getGoalCtx(); @@ -1565,59 +1525,59 @@ index 25fe78116ce01eeefe5c958423734195d27302eb..e19f2cb98c9669deb3a19114264668fb + }; + return; + } -+ // Leaf end - async alert other ++ // Leaf end - Async alert other + List entitiesOfClass = this.mob .level() .getEntitiesOfClass((Class)this.mob.getClass(), aabb, EntitySelector.NO_SPECTATORS); -@@ -86,6 +136,7 @@ public class HurtByTargetGoal extends TargetGoal { - } +@@ -87,7 +137,7 @@ public class HurtByTargetGoal extends TargetGoal { mob = (Mob)var5.next(); -+ // Leaf - async alert other if (this.mob != mob - && mob.getTarget() == null +- && mob.getTarget() == null ++ && mob.getTarget() == null // Leaf - Async alert other - diff on change && (!(this.mob instanceof TamableAnimal) || ((TamableAnimal)this.mob).getOwner() == ((TamableAnimal)mob).getOwner()) -@@ -96,6 +147,7 @@ public class HurtByTargetGoal extends TargetGoal { + && !mob.isAlliedTo(this.mob.getLastHurtByMob())) { + if (this.toIgnoreAlert == null) { +@@ -96,7 +146,7 @@ public class HurtByTargetGoal extends TargetGoal { boolean flag = false; -+ // Leaf - async alert other - for (Class clazz : this.toIgnoreAlert) { +- for (Class clazz : this.toIgnoreAlert) { ++ for (Class clazz : this.toIgnoreAlert) { // Leaf - Async alert other - diff on change if (mob.getClass() == clazz) { flag = true; -@@ -113,6 +165,15 @@ public class HurtByTargetGoal extends TargetGoal { + break; +@@ -113,6 +163,15 @@ public class HurtByTargetGoal extends TargetGoal { } } -+ // Leaf start - async alert other ++ // Leaf start - Async alert other + public void poll() { -+ if (!(this.mob.getGoalCtx().result() instanceof List toAlert)) { return; } ++ if (!(this.mob.getGoalCtx().result() instanceof List toAlert)) return; + for (var livingEntity : toAlert) { + alertOther((Mob) livingEntity, this.mob.getLastHurtByMob()); + } + } -+ // Leaf end - async alert other ++ // Leaf end - Async alert other + protected void alertOther(Mob mob, LivingEntity target) { mob.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY, true); // CraftBukkit - reason } diff --git a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f07d0db481aecb8260c09eafef2c1cd582a8502c 100644 +index 85eae0a14f7a417dfd8c911079d05354a98e5834..3d0c28caa646286be79cefee4710b15f9aad928c 100644 --- a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -@@ -41,8 +41,53 @@ public class NearestAttackableTargetGoal extends TargetG +@@ -41,8 +41,51 @@ public class NearestAttackableTargetGoal extends TargetG this.targetConditions = TargetingConditions.forCombat().range(this.getFollowDistance()).selector(selector); } + // Leaf start - Async Target Finding + protected boolean poll() { -+ if (!(this.mob.getGoalCtx().result() instanceof LivingEntity t)) { return false; } ++ if (!(this.mob.getGoalCtx().result() instanceof LivingEntity target)) return false; + ServerLevel serverLevel = getServerLevel(this.mob); -+ if (serverLevel == null || !t.isAlive() || !this.getTargetConditions().test(serverLevel, this.mob, t)) { -+ return false; -+ } -+ this.target = t; ++ if (serverLevel == null || !target.isAlive() || !this.getTargetConditions().test(serverLevel, this.mob, target)) return false; ++ this.target = target; + return true; + } + @@ -1660,7 +1620,7 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f07d0db481aecb8260c09eafef2c1cd5 if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) { return false; } else { -@@ -57,6 +102,15 @@ public class NearestAttackableTargetGoal extends TargetG +@@ -57,6 +100,15 @@ public class NearestAttackableTargetGoal extends TargetG protected void findTarget() { ServerLevel serverLevel = getServerLevel(this.mob); @@ -1676,15 +1636,6 @@ index 41ee3cdc45ecc8376a2203ed588bb544ed377294..f07d0db481aecb8260c09eafef2c1cd5 if (this.targetType != Player.class && this.targetType != ServerPlayer.class) { this.target = serverLevel.getNearestEntity( this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true), -@@ -81,7 +135,7 @@ public class NearestAttackableTargetGoal extends TargetG - this.target = target; - } - -- private TargetingConditions getTargetConditions() { -+ protected TargetingConditions getTargetConditions() { - return this.targetConditions.range(this.getFollowDistance()); - } - } diff --git a/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java index 4604a603c4ddd0a9242e859aaa5a511c2d4c4f84..7274dfb52ca4a08cdebcd04294cedc73460593e5 100644 --- a/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java @@ -1707,7 +1658,7 @@ index 4604a603c4ddd0a9242e859aaa5a511c2d4c4f84..7274dfb52ca4a08cdebcd04294cedc73 } else if (!((Raider)this.mob).hasActiveRaid()) { return false; diff --git a/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java -index abf57494950f55bbd75f335f26736cb9e703c197..683722fbd07fcae9cdd72928ed25fd084202b57e 100644 +index abf57494950f55bbd75f335f26736cb9e703c197..efd2418f56c36e7850edde483a2a4906dd622441 100644 --- a/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java @@ -20,6 +20,15 @@ public class NonTameRandomTargetGoal extends NearestAtta @@ -1715,7 +1666,7 @@ index abf57494950f55bbd75f335f26736cb9e703c197..683722fbd07fcae9cdd72928ed25fd08 @Override public boolean canContinueToUse() { - return this.targetConditions != null ? this.targetConditions.test(getServerLevel(this.mob), this.mob, this.target) : super.canContinueToUse(); -+ // Leaf start ++ // Leaf start - Async target finding + if (this.target == null || !this.target.isAlive() || this.target.isRemoved()) { + return false; + } @@ -1724,18 +1675,18 @@ index abf57494950f55bbd75f335f26736cb9e703c197..683722fbd07fcae9cdd72928ed25fd08 + return false; + } + return this.getTargetConditions().test(serverLevel, this.mob, this.target) && super.canContinueToUse(); -+ // Leaf end ++ // Leaf end - Async target finding } } diff --git a/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java -index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..93da18d921a1cf7d72f441d9415435ff9c417f1f 100644 +index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..15da909b4db3d7a7532aed550208ecbb76144b37 100644 --- a/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/ResetUniversalAngerTargetGoal.java @@ -37,6 +37,34 @@ public class ResetUniversalAngerTargetGoal extends G this.lastHurtByPlayerTimestamp = this.mob.getLastHurtByMobTimestamp(); this.mob.forgetCurrentTargetAndRefreshUniversalAnger(); if (this.alertOthersOfSameType) { -+ // Leaf start - async alert other ++ // Leaf start - Async alert other + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.alertOther) { + final var mob = this.mob; + final var ctx = mob.getGoalCtx(); @@ -1762,7 +1713,7 @@ index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..93da18d921a1cf7d72f441d9415435ff + }; + return; + } -+ // Leaf end - async alert other ++ // Leaf end - Async alert other this.getNearbyMobsOfSameType() .stream() .filter(mob -> mob != this.mob) @@ -1770,80 +1721,73 @@ index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..93da18d921a1cf7d72f441d9415435ff super.start(); } -+ // Leaf start - async alert other ++ // Leaf start - Async alert other + public void poll() { -+ if (!(this.mob.getGoalCtx().result() instanceof List toStop)) { return; } ++ if (!(this.mob.getGoalCtx().result() instanceof List toStop)) return; + for (var neutralMob : toStop) { + ((NeutralMob)neutralMob).forgetCurrentTargetAndRefreshUniversalAnger(); + } + } -+ // Leaf end - async alert other ++ // Leaf end - Async alert other + private List getNearbyMobsOfSameType() { -+ // Leaf - async alert other ++ // Leaf - Async alert other - diff on change double attributeValue = this.mob.getAttributeValue(Attributes.FOLLOW_RANGE); AABB aabb = AABB.unitCubeFromLowerCorner(this.mob.position()).inflate(attributeValue, 10.0, attributeValue); return this.mob.level().getEntitiesOfClass((Class)this.mob.getClass(), aabb, EntitySelector.NO_SPECTATORS); diff --git a/net/minecraft/world/entity/ai/sensing/Sensing.java b/net/minecraft/world/entity/ai/sensing/Sensing.java -index 002d3c0d8b1107a275020d5c582c37e9a5c536ee..3f8c18f4040f4929df79ba85906330b150720cb0 100644 +index 002d3c0d8b1107a275020d5c582c37e9a5c536ee..6fa0b8defbd1d06b3bf5d9b32ffd08f32c1fb707 100644 --- a/net/minecraft/world/entity/ai/sensing/Sensing.java +++ b/net/minecraft/world/entity/ai/sensing/Sensing.java -@@ -32,9 +32,21 @@ public class Sensing { - // Gale end - Petal - reduce line of sight updates - expiring entity id lists +@@ -33,6 +33,17 @@ public class Sensing { } -+ // Leaf start - async target finding public void tick() { ++ // Leaf start - Async target finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { + synchronized (this) { -+ tick1(); ++ tickInternal(); + } + } else { -+ tick1(); ++ tickInternal(); + } + } -+ -+ private void tick1() { -+ // Leaf end - async target finding ++ private void tickInternal() { ++ // Leaf end - Async target finding if (this.expiring == null) { // Gale - Petal - reduce line of sight updates -- this.seen.clear(); -+ this.seen.clear(); + this.seen.clear(); // Gale start - Petal - reduce line of sight updates - } else { - var expiringNow = this.expiring[this.nextToExpireIndex]; -@@ -62,7 +74,19 @@ public class Sensing { - // Gale end - Petal - reduce line of sight updates +@@ -63,6 +74,17 @@ public class Sensing { } -+ // Leaf start - async target finding public boolean hasLineOfSight(Entity entity) { ++ // Leaf start - Async target finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { + synchronized (this) { -+ return hasLineOfSight1(entity); ++ return hasLineOfSightInternal(entity); + } + } else { -+ return hasLineOfSight1(entity); ++ return hasLineOfSightInternal(entity); + } + } -+ -+ private boolean hasLineOfSight1(Entity entity) { -+ // Leaf end - async target finding ++ private boolean hasLineOfSightInternal(Entity entity) { ++ // Leaf end - Async target finding int id = entity.getId(); // Gale start - Petal - reduce line of sight cache lookups - merge sets int cached = this.seen.get(id); diff --git a/net/minecraft/world/entity/animal/Fox.java b/net/minecraft/world/entity/animal/Fox.java -index 90452f0945e761077608692877677f522d38bccd..9e380f5b4dcedb115f8893dd382f28ea05fab121 100644 +index 90452f0945e761077608692877677f522d38bccd..5c0f0ae7bb1aa379487816b115e43140dd27aadc 100644 --- a/net/minecraft/world/entity/animal/Fox.java +++ b/net/minecraft/world/entity/animal/Fox.java @@ -849,13 +849,18 @@ public class Fox extends Animal implements VariantHolder { return false; } else { ServerLevel serverLevel = getServerLevel(Fox.this.level()); -+ // Leaf start ++ // Leaf start - Async target finding + if (serverLevel == null) { + return false; + } -+ // Leaf end ++ // Leaf end - Async target finding for (UUID uuid : Fox.this.getTrustedUUIDs()) { if (serverLevel.getEntity(uuid) instanceof LivingEntity livingEntity) { @@ -1851,19 +1795,20 @@ index 90452f0945e761077608692877677f522d38bccd..9e380f5b4dcedb115f8893dd382f28ea this.trustedLastHurtBy = livingEntity.getLastHurtByMob(); int lastHurtByMobTimestamp = livingEntity.getLastHurtByMobTimestamp(); - return lastHurtByMobTimestamp != this.timestamp && this.canAttack(this.trustedLastHurtBy, this.targetConditions); -+ return lastHurtByMobTimestamp != this.timestamp && this.canAttack(this.trustedLastHurtBy, this.getTargetConditions()); // Leaf ++ return lastHurtByMobTimestamp != this.timestamp && this.canAttack(this.trustedLastHurtBy, this.getTargetConditions()); // Leaf - Async target finding - diff on change } } -@@ -1032,6 +1037,7 @@ public class Fox extends Animal implements VariantHolder { - +@@ -1033,7 +1038,7 @@ public class Fox extends Animal implements VariantHolder { @Override protected boolean isValidTarget(LevelReader level, BlockPos pos) { -+ // Leaf - Async search block BlockState blockState = level.getBlockState(pos); - return blockState.is(Blocks.SWEET_BERRY_BUSH) && blockState.getValue(SweetBerryBushBlock.AGE) >= 2 || CaveVines.hasGlowBerries(blockState); +- return blockState.is(Blocks.SWEET_BERRY_BUSH) && blockState.getValue(SweetBerryBushBlock.AGE) >= 2 || CaveVines.hasGlowBerries(blockState); ++ return blockState.is(Blocks.SWEET_BERRY_BUSH) && blockState.getValue(SweetBerryBushBlock.AGE) >= 2 || CaveVines.hasGlowBerries(blockState); // Leaf - Async search block - diff on change } -@@ -1097,6 +1103,13 @@ public class Fox extends Animal implements VariantHolder { + + @Override +@@ -1097,6 +1102,13 @@ public class Fox extends Animal implements VariantHolder { Fox.this.setSitting(false); super.start(); } @@ -1878,10 +1823,10 @@ index 90452f0945e761077608692877677f522d38bccd..9e380f5b4dcedb115f8893dd382f28ea class FoxFloatGoal extends FloatGoal { diff --git a/net/minecraft/world/entity/animal/Panda.java b/net/minecraft/world/entity/animal/Panda.java -index 0a6a3060f3690ab2d8439d66e6fd6f0c5dc20688..d3466ebc9d0a57d0dcefa98d65fe42d8c827e6d3 100644 +index 0a6a3060f3690ab2d8439d66e6fd6f0c5dc20688..d99a1f6baebdc7c4a700e3fee30d1b52f3c1ac3d 100644 --- a/net/minecraft/world/entity/animal/Panda.java +++ b/net/minecraft/world/entity/animal/Panda.java -@@ -991,31 +991,39 @@ public class Panda extends Animal { +@@ -991,9 +991,18 @@ public class Panda extends Animal { @Override public boolean canUse() { @@ -1891,59 +1836,17 @@ index 0a6a3060f3690ab2d8439d66e6fd6f0c5dc20688..d3466ebc9d0a57d0dcefa98d65fe42d8 + } if (this.mob.getRandom().nextFloat() >= this.probability) { return false; -- } else { -- if (this.lookAt == null) { -- ServerLevel serverLevel = getServerLevel(this.mob); -- if (this.lookAtType == Player.class) { -- this.lookAt = serverLevel.getNearestPlayer(this.lookAtContext, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); -- } else { -- this.lookAt = serverLevel.getNearestEntity( -- this.mob -- .level() -- .getEntitiesOfClass( -- this.lookAtType, this.mob.getBoundingBox().inflate(this.lookDistance, 3.0, this.lookDistance), entity -> true -- ), -- this.lookAtContext, -- this.mob, -- this.mob.getX(), -- this.mob.getEyeY(), -- this.mob.getZ() -- ); -- } -+ } -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { -+ getLookAsync(); -+ return false; -+ } -+ if (this.lookAt == null) { -+ ServerLevel serverLevel = getServerLevel(this.mob); -+ if (this.lookAtType == Player.class) { -+ this.lookAt = serverLevel.getNearestPlayer(this.lookAtContext, this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); -+ } else { -+ this.lookAt = serverLevel.getNearestEntity( -+ this.mob -+ .level() -+ .getEntitiesOfClass( -+ this.lookAtType, this.mob.getBoundingBox().inflate(this.lookDistance, 3.0, this.lookDistance), entity -> true -+ ), -+ this.lookAtContext, -+ this.mob, -+ this.mob.getX(), -+ this.mob.getEyeY(), -+ this.mob.getZ() -+ ); - } -- -- return this.panda.canPerformAction() && this.lookAt != null; - } -+ -+ return this.panda.canPerformAction() && this.lookAt != null; -+ // Leaf end - Async look finding - } - - @Override + } else { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ getLookAsync(); ++ return false; ++ } ++ // Leaf end - Async look finding + if (this.lookAt == null) { + ServerLevel serverLevel = getServerLevel(this.mob); + if (this.lookAtType == Player.class) { diff --git a/net/minecraft/world/entity/animal/Rabbit.java b/net/minecraft/world/entity/animal/Rabbit.java -index da5b32a17283e540615373097acc511d928aeff5..a9cc92d2ae2b1548b27288c190a3f99799dcefbe 100644 +index da5b32a17283e540615373097acc511d928aeff5..cdab995c86978f0a789c85d14151abf19d89b673 100644 --- a/net/minecraft/world/entity/animal/Rabbit.java +++ b/net/minecraft/world/entity/animal/Rabbit.java @@ -642,7 +642,13 @@ public class Rabbit extends Animal implements VariantHolder { @@ -1951,29 +1854,26 @@ index da5b32a17283e540615373097acc511d928aeff5..a9cc92d2ae2b1548b27288c190a3f997 } - return super.canUse(); -+ // Leaf start ++ // Leaf start - Async search block + if (this.wantsToRaid && !this.canRaid) { + return super.canUse(); + } else { + return false; + } -+ // Leaf end ++ // Leaf end - Async search block } @Override -@@ -684,6 +690,7 @@ public class Rabbit extends Animal implements VariantHolder { - +@@ -685,7 +691,7 @@ public class Rabbit extends Animal implements VariantHolder { @Override protected boolean isValidTarget(LevelReader level, BlockPos pos) { -+ // Leaf - Async search block BlockState blockState = level.getBlockState(pos); - if (blockState.is(Blocks.FARMLAND) && this.wantsToRaid && !this.canRaid) { +- if (blockState.is(Blocks.FARMLAND) && this.wantsToRaid && !this.canRaid) { ++ if (blockState.is(Blocks.FARMLAND) && this.wantsToRaid && !this.canRaid) { // Leaf - Async search block - diff on change blockState = level.getBlockState(pos.above()); -@@ -692,9 +699,17 @@ public class Rabbit extends Animal implements VariantHolder { - return true; - } - } -+ // Leaf - Async search block + if (blockState.getBlock() instanceof CarrotBlock && ((CarrotBlock)blockState.getBlock()).isMaxAge(blockState)) { + this.canRaid = true; +@@ -695,6 +701,13 @@ public class Rabbit extends Animal implements VariantHolder { return false; } @@ -1988,15 +1888,15 @@ index da5b32a17283e540615373097acc511d928aeff5..a9cc92d2ae2b1548b27288c190a3f997 public static enum Variant implements StringRepresentable { diff --git a/net/minecraft/world/entity/animal/Turtle.java b/net/minecraft/world/entity/animal/Turtle.java -index 10477fea8fcd70bf0c1ba4b6e1113625be690e68..65bbfc7d0078465faa6f18ff2f611d813fea310a 100644 +index 10477fea8fcd70bf0c1ba4b6e1113625be690e68..c5f626dcfe724f8ed19303d305f6eb5cfcc0f0af 100644 --- a/net/minecraft/world/entity/animal/Turtle.java +++ b/net/minecraft/world/entity/animal/Turtle.java -@@ -527,8 +527,16 @@ public class Turtle extends Animal { +@@ -527,8 +527,15 @@ public class Turtle extends Animal { @Override protected boolean isValidTarget(LevelReader level, BlockPos pos) { -+ // Leaf - Async search block - return level.getBlockState(pos).is(Blocks.WATER); +- return level.getBlockState(pos).is(Blocks.WATER); ++ return level.getBlockState(pos).is(Blocks.WATER); // Leaf - Async search block - diff on change } + + // Leaf start - Async search block @@ -2008,48 +1908,50 @@ index 10477fea8fcd70bf0c1ba4b6e1113625be690e68..65bbfc7d0078465faa6f18ff2f611d81 } static class TurtleLayEggGoal extends MoveToBlockGoal { -@@ -584,8 +592,16 @@ public class Turtle extends Animal { +@@ -584,8 +591,15 @@ public class Turtle extends Animal { @Override protected boolean isValidTarget(LevelReader level, BlockPos pos) { -+ // Leaf - Async search block - return level.isEmptyBlock(pos.above()) && TurtleEggBlock.isSand(level, pos); - } +- return level.isEmptyBlock(pos.above()) && TurtleEggBlock.isSand(level, pos); ++ return level.isEmptyBlock(pos.above()) && TurtleEggBlock.isSand(level, pos); // Leaf - Async search block - diff on change ++ } + + // Leaf start - Async search block + @Override + protected TypeToCheck typeToCheck() { + return TypeToCheck.TurtleLay; -+ } + } + // Leaf end - Async search block } static class TurtleMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables diff --git a/net/minecraft/world/entity/animal/Wolf.java b/net/minecraft/world/entity/animal/Wolf.java -index 7cb292de6b27fa4ba3c5fce526a4e939c576789f..856bfeaa91a476c1008909ac35f76dfe0b792c9c 100644 +index 7cb292de6b27fa4ba3c5fce526a4e939c576789f..68852158e2fd3c4ce241d722ac414d73ea3b8836 100644 --- a/net/minecraft/world/entity/animal/Wolf.java +++ b/net/minecraft/world/entity/animal/Wolf.java -@@ -672,6 +672,7 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder list = Lists.newArrayList(); // Leaf - unused ++ // List list = Lists.newArrayList(); // Leaf - Async target finding - unused ++ // Leaf end - Async target finding // Paper start - rewrite chunk system final List ret = new java.util.ArrayList<>(); diff --git a/leaf-server/minecraft-patches/features/0155-Optimize-ThreadedTicketLevelPropagator.patch b/leaf-server/minecraft-patches/features/0155-Optimize-ThreadedTicketLevelPropagator.patch index ce2f704e..9c5503b2 100644 --- a/leaf-server/minecraft-patches/features/0155-Optimize-ThreadedTicketLevelPropagator.patch +++ b/leaf-server/minecraft-patches/features/0155-Optimize-ThreadedTicketLevelPropagator.patch @@ -5,33 +5,36 @@ Subject: [PATCH] Optimize ThreadedTicketLevelPropagator diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java -index 01598347ea545f2ff2ac337086345d7369a64520..fbea9d83b7458760d4b1b5e1c8bd77ff46072b97 100644 +index 3a7fad46465cac8d2c1b0933b457f5b075586709..a2d76e6fabf2749a1a9f21fe6bdf6524af8bb9b7 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ThreadedTicketLevelPropagator.java @@ -998,6 +998,7 @@ public abstract class ThreadedTicketLevelPropagator { final int decodeOffsetZ = -this.encodeOffsetZ; final int encodeOffset = this.coordinateOffset; final int sectionOffset = this.sectionIndexOffset; -+ final Section[] sectionsArray = this.sections; ++ final Section[] sectionsArray = this.sections; // Leaf - Optimize ThreadedTicketLevelPropagator final Long2ByteLinkedOpenHashMap updatedPositions = this.updatedPositions; -@@ -1012,13 +1013,23 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1012,13 +1013,27 @@ public abstract class ThreadedTicketLevelPropagator { int propagateDirectionBitset = (int)(queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); if ((queueValue & FLAG_RECHECK_LEVEL) != 0L) { - if (this.getLevel(posX, posZ) != propagatedLevel) { ++ // Leaf start - Optimize ThreadedTicketLevelPropagator + final int sectionX = posX >> SECTION_SHIFT; + final int sectionZ = posZ >> SECTION_SHIFT; + final Section section = sectionsArray[sectionX + (sectionZ * SECTION_CACHE_WIDTH) + sectionOffset]; + final int localIdx = (posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); + if ((section.levels[localIdx] & 0xFF) != propagatedLevel) { ++ // Leaf end - Optimize ThreadedTicketLevelPropagator // not at the level we expect, so something changed. continue; } } else if ((queueValue & FLAG_WRITE_LEVEL) != 0L) { // these are used to restore sources after a propagation decrease - this.setLevel(posX, posZ, propagatedLevel); ++ // Leaf start - Optimize ThreadedTicketLevelPropagator + final int sectionX = posX >> SECTION_SHIFT; + final int sectionZ = posZ >> SECTION_SHIFT; + final Section section = sectionsArray[sectionX + (sectionZ * SECTION_CACHE_WIDTH) + sectionOffset]; @@ -39,32 +42,33 @@ index 01598347ea545f2ff2ac337086345d7369a64520..fbea9d83b7458760d4b1b5e1c8bd77ff + final short currentLevel = section.levels[localIdx]; + section.levels[localIdx] = (short) ((currentLevel & ~0xFF) | (propagatedLevel & 0xFF)); + updatedPositions.put(CoordinateUtils.getChunkKey(posX, posZ), (byte) propagatedLevel); ++ // Leaf end - Optimize ThreadedTicketLevelPropagator } // this bitset represents the values that we have not propagated to -@@ -1093,7 +1104,7 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1093,7 +1108,7 @@ public abstract class ThreadedTicketLevelPropagator { currentPropagation ^= (bitsetLine1 | bitsetLine2 | bitsetLine3); // now try to propagate - final Section section = this.sections[sectionIndex]; -+ final Section section = sectionsArray[sectionIndex]; ++ final Section section = sectionsArray[sectionIndex]; // Leaf - Optimize ThreadedTicketLevelPropagator // lower 8 bits are current level, next upper 7 bits are source level, next 1 bit is updated source flag final short currentStoredLevel = section.levels[localIndex]; -@@ -1144,6 +1155,7 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1144,6 +1159,7 @@ public abstract class ThreadedTicketLevelPropagator { final int decodeOffsetZ = -this.encodeOffsetZ; final int encodeOffset = this.coordinateOffset; final int sectionOffset = this.sectionIndexOffset; -+ final Section[] sectionsArray = this.sections; ++ final Section[] sectionsArray = this.sections; // Leaf - Optimize ThreadedTicketLevelPropagator final Long2ByteLinkedOpenHashMap updatedPositions = this.updatedPositions; -@@ -1227,7 +1239,7 @@ public abstract class ThreadedTicketLevelPropagator { +@@ -1227,7 +1243,7 @@ public abstract class ThreadedTicketLevelPropagator { final long bitsetLine3 = currentPropagation & (7L << (start + (8 + 8))); // now try to propagate - final Section section = this.sections[sectionIndex]; -+ final Section section = sectionsArray[sectionIndex]; ++ final Section section = sectionsArray[sectionIndex]; // Leaf - Optimize ThreadedTicketLevelPropagator // lower 8 bits are current level, next upper 7 bits are source level, next 1 bit is updated source flag final short currentStoredLevel = section.levels[localIndex]; diff --git a/leaf-server/minecraft-patches/features/0156-Optimise-MobEffectUtil-getDigSpeedAmplification.patch b/leaf-server/minecraft-patches/features/0156-Optimise-MobEffectUtil-getDigSpeedAmplification.patch index 3550d539..dc4502f7 100644 --- a/leaf-server/minecraft-patches/features/0156-Optimise-MobEffectUtil-getDigSpeedAmplification.patch +++ b/leaf-server/minecraft-patches/features/0156-Optimise-MobEffectUtil-getDigSpeedAmplification.patch @@ -5,25 +5,35 @@ Subject: [PATCH] Optimise MobEffectUtil#getDigSpeedAmplification diff --git a/net/minecraft/world/effect/MobEffectUtil.java b/net/minecraft/world/effect/MobEffectUtil.java -index cbf1b6af928aa439c3264b302e5f1a1ddd4c14f0..c59a503ef8bc2dabcf9f7c85c8d93fb1fcadf71f 100644 +index cbf1b6af928aa439c3264b302e5f1a1ddd4c14f0..e4126bbfb46f2d3df9aac4c9362b9d81b3ecb713 100644 --- a/net/minecraft/world/effect/MobEffectUtil.java +++ b/net/minecraft/world/effect/MobEffectUtil.java -@@ -29,12 +29,14 @@ public final class MobEffectUtil { +@@ -27,17 +27,21 @@ public final class MobEffectUtil { + } + public static int getDigSpeedAmplification(LivingEntity entity) { - int i = 0; - int i1 = 0; +- int i = 0; +- int i1 = 0; - if (entity.hasEffect(MobEffects.DIG_SPEED)) { - i = entity.getEffect(MobEffects.DIG_SPEED).getAmplifier(); ++ // Leaf start - Optimise MobEffectUtil#getDigSpeedAmplification ++ int digAmplifier = 0; ++ int conduitAmplifier = 0; + MobEffectInstance digEffect = entity.getEffect(MobEffects.DIG_SPEED); + if (digEffect != null) { -+ i = digEffect.getAmplifier(); ++ digAmplifier = digEffect.getAmplifier(); } - if (entity.hasEffect(MobEffects.CONDUIT_POWER)) { - i1 = entity.getEffect(MobEffects.CONDUIT_POWER).getAmplifier(); + MobEffectInstance conduitEffect = entity.getEffect(MobEffects.CONDUIT_POWER); + if (conduitEffect != null) { -+ i1 = conduitEffect.getAmplifier(); ++ conduitAmplifier = conduitEffect.getAmplifier(); } - return Math.max(i, i1); +- return Math.max(i, i1); ++ return Math.max(digAmplifier, conduitAmplifier); ++ // Leaf end - Optimise MobEffectUtil#getDigSpeedAmplification + } + + public static boolean hasWaterBreathing(LivingEntity entity) { diff --git a/leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch b/leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch index 61b8d35b..1e8b498c 100644 --- a/leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch +++ b/leaf-server/minecraft-patches/features/0157-Optimise-chunkUnloads.patch @@ -212,10 +212,10 @@ index b8ac6a9ba7b56ccd034757f7d135d272b8e69e90..dc158e981199b507531af810ff9ced3c } } diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java -index e9ece9b618b0a9eb82b9f07a09ee6cb60cf7ec16..18d2ec110fc6670edb079eccf448389dc365eb88 100644 +index e9ece9b618b0a9eb82b9f07a09ee6cb60cf7ec16..dff2fed559a274ee7896ee136999eaf288cf82bb 100644 --- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java -@@ -526,14 +526,14 @@ public record SerializableChunkData( +@@ -526,14 +526,16 @@ public record SerializableChunkData( throw new IllegalArgumentException("Chunk can't be serialized: " + chunk); } else { ChunkPos pos = chunk.getPos(); @@ -227,16 +227,19 @@ index e9ece9b618b0a9eb82b9f07a09ee6cb60cf7ec16..18d2ec110fc6670edb079eccf448389d final int minLightSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinLightSection(level); final int maxLightSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxLightSection(level); final int minBlockSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(level); ++ // Leaf start - Optimize chunkUnload + // Pre-allocate with correct capacity to avoid resizing + final int expectedSectionCount = maxLightSection - minLightSection + 1; + List list = new ArrayList<>(expectedSectionCount); ++ // Leaf end - Optimize chunkUnload final LevelChunkSection[] chunkSections = chunk.getSections(); final ca.spottedleaf.moonrise.patches.starlight.light.SWMRNibbleArray[] blockNibbles = ((ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk)chunk).starlight$getBlockNibbles(); -@@ -551,10 +551,18 @@ public record SerializableChunkData( +@@ -551,10 +553,20 @@ public record SerializableChunkData( continue; } ++ // Leaf start - Optimize chunkUnload + DataLayer blockDataLayer = null; + if (blockNibble != null && blockNibble.data != null) { + blockDataLayer = new DataLayer(blockNibble.data); @@ -246,50 +249,48 @@ index e9ece9b618b0a9eb82b9f07a09ee6cb60cf7ec16..18d2ec110fc6670edb079eccf448389d + if (skyNibble != null && skyNibble.data != null) { + skyDataLayer = new DataLayer(skyNibble.data); + } ++ // Leaf end - Optimize chunkUnload + final SerializableChunkData.SectionData sectionData = new SerializableChunkData.SectionData( - lightSection, chunkSection, - blockNibble == null ? null : (blockNibble.data == null ? null : new DataLayer(blockNibble.data)), - skyNibble == null ? null : (skyNibble.data == null ? null : new DataLayer(skyNibble.data)) -+ lightSection, chunkSection, blockDataLayer, skyDataLayer ++ lightSection, chunkSection, blockDataLayer, skyDataLayer // Leaf - Optimize chunkUnload ); if (blockNibble != null) { -@@ -565,28 +573,42 @@ public record SerializableChunkData( +@@ -565,12 +577,16 @@ public record SerializableChunkData( ((ca.spottedleaf.moonrise.patches.starlight.storage.StarlightSectionData)(Object)sectionData).starlight$setSkyLightState(skyNibble.state); } - sectionsList.add(sectionData); -+ list.add(sectionData); ++ list.add(sectionData); // Leaf - Optimize chunkUnload } // Paper end - starlight - List list1 = new ArrayList<>(chunk.getBlockEntitiesPos().size()); ++ // Leaf start - Optimize chunkUnload + // Pre-allocate block entities list with exact size needed + final int blockEntityCount = chunk.getBlockEntitiesPos().size(); + List list1 = blockEntityCount > 0 ? new ArrayList<>(blockEntityCount) : java.util.Collections.emptyList(); -- for (BlockPos blockPos : chunk.getBlockEntitiesPos()) { -- CompoundTag blockEntityNbtForSaving = chunk.getBlockEntityNbtForSaving(blockPos, level.registryAccess()); -- if (blockEntityNbtForSaving != null) { -- list1.add(blockEntityNbtForSaving); -+ if (blockEntityCount > 0) { -+ for (BlockPos blockPos : chunk.getBlockEntitiesPos()) { -+ CompoundTag blockEntityNbtForSaving = chunk.getBlockEntityNbtForSaving(blockPos, level.registryAccess()); -+ if (blockEntityNbtForSaving != null) { -+ list1.add(blockEntityNbtForSaving); -+ } ++ if (blockEntityCount > 0) // Leaf - Optimize chunkUnload + for (BlockPos blockPos : chunk.getBlockEntitiesPos()) { + CompoundTag blockEntityNbtForSaving = chunk.getBlockEntityNbtForSaving(blockPos, level.registryAccess()); + if (blockEntityNbtForSaving != null) { +@@ -578,15 +594,27 @@ public record SerializableChunkData( } } - List list2 = new ArrayList<>(); ++ // Leaf start - Optimize chunkUnload + // For entities, use an initial estimated capacity if it's a ProtoChunk + List list2; long[] longs = null; -+ if (chunk.getPersistedStatus().getChunkType() == ChunkType.PROTOCHUNK) { ProtoChunk protoChunk = (ProtoChunk)chunk; - list2.addAll(protoChunk.getEntities()); ++ // Leaf start - Optimize chunkUnload + int entitySize = protoChunk.getEntities().size(); + if (entitySize > 0) { + list2 = new ArrayList<>(Math.max(16, entitySize)); @@ -297,21 +298,22 @@ index e9ece9b618b0a9eb82b9f07a09ee6cb60cf7ec16..18d2ec110fc6670edb079eccf448389d + } else { + list2 = java.util.Collections.emptyList(); + } ++ // Leaf end - Optimize chunkUnload CarvingMask carvingMask = protoChunk.getCarvingMask(); if (carvingMask != null) { longs = carvingMask.toArray(); } + } else { -+ list2 = java.util.Collections.emptyList(); ++ list2 = java.util.Collections.emptyList(); // Leaf - Optimize chunkUnload } Map map = new EnumMap<>(Heightmap.Types.class); -@@ -594,14 +616,25 @@ public record SerializableChunkData( +@@ -594,14 +622,26 @@ public record SerializableChunkData( for (Entry entry : chunk.getHeightmaps()) { if (chunk.getPersistedStatus().heightmapsAfter().contains(entry.getKey())) { long[] rawData = entry.getValue().getRawData(); - map.put(entry.getKey(), (long[])rawData.clone()); -+ map.put(entry.getKey(), Arrays.copyOf(rawData, rawData.length)); ++ map.put(entry.getKey(), Arrays.copyOf(rawData, rawData.length)); // Leaf - Optimize chunkUnload } } @@ -319,7 +321,7 @@ index e9ece9b618b0a9eb82b9f07a09ee6cb60cf7ec16..18d2ec110fc6670edb079eccf448389d - ShortList[] lists = Arrays.stream(chunk.getPostProcessing()) - .map(list3 -> list3 != null ? new ShortArrayList(list3) : null) - .toArray(ShortList[]::new); -+ // Leaf start - Some Optimizations on SerializableChunkData ++ // Leaf start - Optimize chunkUnload - remove stream + ShortList[] postProcessing = chunk.getPostProcessing(); + ShortList[] lists = new ShortList[postProcessing.length]; + for (int i = 0; i < postProcessing.length; i++) { @@ -333,6 +335,7 @@ index e9ece9b618b0a9eb82b9f07a09ee6cb60cf7ec16..18d2ec110fc6670edb079eccf448389d + } + } + } ++ // Leaf end - Optimize chunkUnload - remove stream CompoundTag compoundTag = packStructureData( StructurePieceSerializationContext.fromLevel(level), pos, chunk.getAllStarts(), chunk.getAllReferences() ); diff --git a/leaf-server/minecraft-patches/features/0158-optimize-BlockEntityType-isValid.patch b/leaf-server/minecraft-patches/features/0158-Optimize-BlockEntityType-isValid.patch similarity index 79% rename from leaf-server/minecraft-patches/features/0158-optimize-BlockEntityType-isValid.patch rename to leaf-server/minecraft-patches/features/0158-Optimize-BlockEntityType-isValid.patch index d1814b1c..7f0720d6 100644 --- a/leaf-server/minecraft-patches/features/0158-optimize-BlockEntityType-isValid.patch +++ b/leaf-server/minecraft-patches/features/0158-Optimize-BlockEntityType-isValid.patch @@ -1,25 +1,25 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Sat, 19 Apr 2025 22:12:19 +0800 -Subject: [PATCH] optimize BlockEntityType#isValid +Subject: [PATCH] Optimize BlockEntityType#isValid diff --git a/net/minecraft/world/level/block/entity/BlockEntityType.java b/net/minecraft/world/level/block/entity/BlockEntityType.java -index a2a674f18d7f2c2e50a6b25f9ac3bf4534275976..073b2ca9f1af94934afc39104e0916c97b84ef0e 100644 +index a2a674f18d7f2c2e50a6b25f9ac3bf4534275976..6be69dcd9faa76ef999daaecc237ee3b4d04c182 100644 --- a/net/minecraft/world/level/block/entity/BlockEntityType.java +++ b/net/minecraft/world/level/block/entity/BlockEntityType.java @@ -252,6 +252,14 @@ public class BlockEntityType { private BlockEntityType(BlockEntityType.BlockEntitySupplier factory, Set validBlocks) { this.factory = factory; this.validBlocks = validBlocks; -+ // Leaf start ++ // Leaf start - Optimize BlockEntityType#isValid + for (Block validBlock : validBlocks) { + if (validBlock.blockEntityType != null) { + throw new IllegalStateException("Duplicate block entity type"); + } + validBlock.blockEntityType = this; + } -+ // Leaf end ++ // Leaf end - Optimize BlockEntityType#isValid } @Nullable @@ -28,19 +28,19 @@ index a2a674f18d7f2c2e50a6b25f9ac3bf4534275976..073b2ca9f1af94934afc39104e0916c9 public boolean isValid(BlockState state) { - return this.validBlocks.contains(state.getBlock()); -+ return state.getBlock().blockEntityType == this; // Leaf - remove hash lookup ++ return state.getBlock().blockEntityType == this; // Leaf - Optimize BlockEntityType#isValid - remove hash lookup } @Deprecated diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java -index d35211b0cae66b1a40e89539507e55973313f46f..fa6d4217f222e8d9decde18e967e951dd833a1c7 100644 +index d35211b0cae66b1a40e89539507e55973313f46f..3a49690672bc49259e863e5cde7f14d57c89fcd5 100644 --- a/net/minecraft/world/level/block/state/BlockBehaviour.java +++ b/net/minecraft/world/level/block/state/BlockBehaviour.java @@ -100,6 +100,7 @@ public abstract class BlockBehaviour implements FeatureElement { protected final BlockBehaviour.Properties properties; protected final Optional> drops; protected final String descriptionId; -+ @Nullable public net.minecraft.world.level.block.entity.BlockEntityType blockEntityType = null; // Leaf ++ @Nullable public net.minecraft.world.level.block.entity.BlockEntityType blockEntityType = null; // Leaf - Optimize BlockEntityType#isValid public BlockBehaviour(BlockBehaviour.Properties properties) { this.hasCollision = properties.hasCollision; diff --git a/leaf-server/paper-patches/features/0004-Pufferfish-Optimize-mob-spawning.patch b/leaf-server/paper-patches/features/0004-Pufferfish-Optimize-mob-spawning.patch index e9697ac3..9850f3b4 100644 --- a/leaf-server/paper-patches/features/0004-Pufferfish-Optimize-mob-spawning.patch +++ b/leaf-server/paper-patches/features/0004-Pufferfish-Optimize-mob-spawning.patch @@ -23,7 +23,7 @@ and, in my opinion, worth the low risk of minor mob-spawning-related inconsistencies. diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java -index c21e00812f1aaa1279834a0562d360d6b89e146c..19e0bf50cf8863f42ce19c3d0d8911c0c9b8ad4a 100644 +index c21e00812f1aaa1279834a0562d360d6b89e146c..4a6bfc2a09401b4c96d6f368ead7b060dae2a08b 100644 --- a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java +++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java @@ -10,7 +10,7 @@ public final class IteratorSafeOrderedReferenceSet { @@ -44,22 +44,7 @@ index c21e00812f1aaa1279834a0562d360d6b89e146c..19e0bf50cf8863f42ce19c3d0d8911c0 public IteratorSafeOrderedReferenceSet() { this(16, 0.75f, 16, 0.2); -@@ -74,16 +74,26 @@ public final class IteratorSafeOrderedReferenceSet { - } - */ - -+ // Pufferfish start - async mob spawning -+ // TODO: backup plan if the issue persists, don't run -+ // this.set.finishRawIterator() or defrag() off-main -+ /* -+ protected final boolean allowSafeIteration() { -+ return ca.spottedleaf.moonrise.common.util.TickThread.isTickThread(); -+ } -+ */ -+ // Pufferfish end - async mob spawning -+ - private double getFragFactor() { - return 1.0 - ((double)this.indexMap.size() / (double)this.listSize); +@@ -79,11 +79,11 @@ public final class IteratorSafeOrderedReferenceSet { } public int createRawIterator() { @@ -73,7 +58,7 @@ index c21e00812f1aaa1279834a0562d360d6b89e146c..19e0bf50cf8863f42ce19c3d0d8911c0 } } -@@ -100,7 +110,7 @@ public final class IteratorSafeOrderedReferenceSet { +@@ -100,7 +100,7 @@ public final class IteratorSafeOrderedReferenceSet { } public void finishRawIterator() { @@ -82,7 +67,7 @@ index c21e00812f1aaa1279834a0562d360d6b89e146c..19e0bf50cf8863f42ce19c3d0d8911c0 if (this.getFragFactor() >= this.maxFragFactor) { this.defrag(); } -@@ -110,14 +120,17 @@ public final class IteratorSafeOrderedReferenceSet { +@@ -110,14 +110,17 @@ public final class IteratorSafeOrderedReferenceSet { public boolean remove(final E element) { final int index = this.indexMap.removeInt(element); if (index >= 0) { @@ -103,7 +88,7 @@ index c21e00812f1aaa1279834a0562d360d6b89e146c..19e0bf50cf8863f42ce19c3d0d8911c0 this.defrag(); } //this.check(); -@@ -149,14 +162,17 @@ public final class IteratorSafeOrderedReferenceSet { +@@ -149,14 +152,17 @@ public final class IteratorSafeOrderedReferenceSet { } private void defrag() { @@ -123,7 +108,7 @@ index c21e00812f1aaa1279834a0562d360d6b89e146c..19e0bf50cf8863f42ce19c3d0d8911c0 //this.check(); return; } -@@ -166,11 +182,11 @@ public final class IteratorSafeOrderedReferenceSet { +@@ -166,11 +172,11 @@ public final class IteratorSafeOrderedReferenceSet { int lastValidIndex; java.util.Iterator> iterator; @@ -137,7 +122,7 @@ index c21e00812f1aaa1279834a0562d360d6b89e146c..19e0bf50cf8863f42ce19c3d0d8911c0 final E key = backingArray[lastValidIndex - 1]; iterator = this.indexMap.reference2IntEntrySet().fastIterator(new Reference2IntMap.Entry() { @Override -@@ -201,7 +217,7 @@ public final class IteratorSafeOrderedReferenceSet { +@@ -201,7 +207,7 @@ public final class IteratorSafeOrderedReferenceSet { // cleanup end Arrays.fill(backingArray, lastValidIndex, this.listSize, null); this.listSize = lastValidIndex; @@ -146,7 +131,7 @@ index c21e00812f1aaa1279834a0562d360d6b89e146c..19e0bf50cf8863f42ce19c3d0d8911c0 //this.check(); } -@@ -219,7 +235,7 @@ public final class IteratorSafeOrderedReferenceSet { +@@ -219,7 +225,7 @@ public final class IteratorSafeOrderedReferenceSet { } public IteratorSafeOrderedReferenceSet.Iterator iterator(final int flags) { @@ -155,7 +140,7 @@ index c21e00812f1aaa1279834a0562d360d6b89e146c..19e0bf50cf8863f42ce19c3d0d8911c0 return new BaseIterator<>(this, true, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize); } -@@ -306,7 +322,7 @@ public final class IteratorSafeOrderedReferenceSet { +@@ -306,7 +312,7 @@ public final class IteratorSafeOrderedReferenceSet { } this.lastReturned = null; this.finished = true; diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java index 5ac7e6cb..b6a4c632 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java @@ -11,9 +11,11 @@ import org.dreeam.leaf.util.queue.SpscIntQueue; import java.util.concurrent.locks.LockSupport; public class AsyncGoalExecutor { + public static final Logger LOGGER = LogManager.getLogger("Leaf Async Goal"); - final SpscIntQueue queue; - final SpscIntQueue wake; + + protected final SpscIntQueue queue; + protected final SpscIntQueue wake; private final AsyncGoalThread thread; private final ServerLevel serverLevel; private boolean dirty = false; @@ -22,18 +24,18 @@ public class AsyncGoalExecutor { public AsyncGoalExecutor(AsyncGoalThread thread, ServerLevel serverLevel) { this.serverLevel = serverLevel; - queue = new SpscIntQueue(AsyncTargetFinding.queueSize); - wake = new SpscIntQueue(AsyncTargetFinding.queueSize); + this.queue = new SpscIntQueue(AsyncTargetFinding.queueSize); + this.wake = new SpscIntQueue(AsyncTargetFinding.queueSize); this.thread = thread; } boolean wake(int id) { Entity entity = this.serverLevel.getEntities().get(id); - if (entity == null || entity.isRemoved() || !(entity instanceof Mob m)) { + if (entity == null || entity.isRemoved() || !(entity instanceof Mob mob)) { return false; } - m.goalSelector.wake(); - m.targetSelector.wake(); + mob.goalSelector.wake(); + mob.targetSelector.wake(); return true; } @@ -42,7 +44,7 @@ public class AsyncGoalExecutor { int spinCount = 0; while (!this.queue.send(entityId)) { spinCount++; - // Unpark thread after some spinning to help clear the queue + // Unpark the thread after some spinning to help clear the queue if (spinCount > SPIN_LIMIT) { unpark(); spinCount = 0; diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java index 12b78169..d3d236c2 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java @@ -7,6 +7,7 @@ import net.minecraft.server.level.ServerLevel; import java.util.concurrent.locks.LockSupport; public class AsyncGoalThread extends Thread { + private static final int SPIN_TRIES = 1000; public AsyncGoalThread(final MinecraftServer server) { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java index 82560379..0f064e5e 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java @@ -3,6 +3,7 @@ package org.dreeam.leaf.async.ai; import org.jetbrains.annotations.Nullable; public class Waker { + @Nullable public volatile Runnable wake = null; @Nullable diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/world/PWTEventScheduler.java b/leaf-server/src/main/java/org/dreeam/leaf/async/world/PWTEventScheduler.java index eef0d654..c74d8909 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/world/PWTEventScheduler.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/world/PWTEventScheduler.java @@ -6,8 +6,10 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class PWTEventScheduler { + private static volatile PWTEventScheduler instance; private final ExecutorService executor; + private PWTEventScheduler() { this.executor = Executors.newCachedThreadPool( new ThreadFactoryBuilder() diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncChunkSend.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncChunkSend.java index 34fae012..89ef74ed 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncChunkSend.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncChunkSend.java @@ -13,8 +13,7 @@ public class AsyncChunkSend extends ConfigModules { @Override public void onLoaded() { - config.addCommentRegionBased(getBasePath(), - """ + config.addCommentRegionBased(getBasePath(), """ Makes chunk packet preparation and sending asynchronous to improve server performance. This can significantly reduce main thread load when many players are loading chunks.""", """ diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java index 5b19711e..f24fc20f 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPathfinding.java @@ -20,8 +20,7 @@ public class AsyncPathfinding extends ConfigModules { @Override public void onLoaded() { - config.addCommentRegionBased(getBasePath() + ".reject-policy", - """ + config.addCommentRegionBased(getBasePath() + ".reject-policy", """ The policy to use when the queue is full and a new task is submitted. FLUSH_ALL: All pending tasks will be run on server thread. CALLER_RUNS: Newly submitted task will be run on server thread. @@ -56,6 +55,10 @@ public class AsyncPathfinding extends ConfigModules { if (asyncPathfindingQueueSize <= 0) asyncPathfindingQueueSize = asyncPathfindingMaxThreads * 256; - asyncPathfindingRejectPolicy = PathfindTaskRejectPolicy.fromString(config.getString(getBasePath() + ".reject-policy", availableProcessors >= 12 && asyncPathfindingQueueSize < 512 ? PathfindTaskRejectPolicy.FLUSH_ALL.toString() : PathfindTaskRejectPolicy.CALLER_RUNS.toString())); + asyncPathfindingRejectPolicy = PathfindTaskRejectPolicy.fromString(config.getString(getBasePath() + ".reject-policy", + availableProcessors >= 12 && asyncPathfindingQueueSize < 512 + ? PathfindTaskRejectPolicy.FLUSH_ALL.toString() + : PathfindTaskRejectPolicy.CALLER_RUNS.toString()) + ); } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java index ebdad7ae..7b8fecfe 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java @@ -14,8 +14,7 @@ public class AsyncPlayerDataSave extends ConfigModules { @Override public void onLoaded() { - config.addCommentRegionBased(getBasePath(), - """ + config.addCommentRegionBased(getBasePath(), """ Make PlayerData saving asynchronously.""", """ 异步保存玩家数据."""); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java index eb2a5aaf..478f132c 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java @@ -37,6 +37,7 @@ public class AsyncTargetFinding extends ConfigModules { searchBlock = config.getBoolean(getBasePath() + ".async-search-block", true); searchEntity = config.getBoolean(getBasePath() + ".async-search-entity", true); queueSize = config.getInt(getBasePath() + ".queue-size", 4096); + if (queueSize <= 0) { queueSize = 4096; } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java index e18028b4..a6b42058 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/MultithreadedTracker.java @@ -19,15 +19,13 @@ public class MultithreadedTracker extends ConfigModules { @Override public void onLoaded() { - config.addCommentRegionBased(getBasePath(), - """ + config.addCommentRegionBased(getBasePath(), """ Make entity tracking saving asynchronously, can improve performance significantly, especially in some massive entities in small area situations.""", """ 异步实体跟踪, 在实体数量多且密集的情况下效果明显."""); - config.addCommentRegionBased(getBasePath() + ".compat-mode", - """ + config.addCommentRegionBased(getBasePath() + ".compat-mode", """ Enable compat mode ONLY if Citizens or NPC plugins using real entity has installed, Compat mode fixed visible issue with player type NPCs of Citizens, But still recommend to use packet based / virtual entity NPC plugin, e.g. ZNPC Plus, Adyeshach, Fancy NPC or else.""", diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java index 811cb169..1c1c4efc 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java @@ -20,8 +20,7 @@ public class SparklyPaperParallelWorldTicking extends ConfigModules { @Override public void onLoaded() { - config.addCommentRegionBased(getBasePath(), - """ + config.addCommentRegionBased(getBasePath(), """ **Experimental feature** Enables parallel world ticking to improve performance on multi-core systems.""", """ diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/SmoothTeleport.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/SmoothTeleport.java index fb739c28..b9e6dfca 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/SmoothTeleport.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/SmoothTeleport.java @@ -15,8 +15,7 @@ public class SmoothTeleport extends ConfigModules { @Override public void onLoaded() { - enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased( - """ + enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased(""" **Experimental feature** Whether to make a "smooth teleport" when players changing dimension. This requires original world and target world have same logical height to work.""", diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/ChatMessageSignature.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/ChatMessageSignature.java index 08e6a5e0..2abfda55 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/ChatMessageSignature.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/ChatMessageSignature.java @@ -17,8 +17,7 @@ public class ChatMessageSignature extends ConfigModules { Whether or not enable chat message signature, disable will prevent players to report chat messages. And also disables the popup when joining a server without - 'secure chat', such as offline-mode servers. - """, + 'secure chat', such as offline-mode servers.""", """ 是否启用聊天签名, 禁用后玩家无法进行聊天举报.""")); } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/OptimizeNonFlushPacketSending.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/OptimizeNonFlushPacketSending.java index 1bf43512..6855d709 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/OptimizeNonFlushPacketSending.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/OptimizeNonFlushPacketSending.java @@ -20,8 +20,7 @@ public class OptimizeNonFlushPacketSending extends ConfigModules { Optimizes non-flush packet sending by using Netty's lazyExecute method to avoid expensive thread wakeup calls when scheduling packet operations. - Requires server restart to take effect. - """, + Requires server restart to take effect.""", """ 警告: 此选项与 ProtocolLib 不兼容, 并可能导致与其他修改数据包 处理的插件出现问题. @@ -29,7 +28,6 @@ public class OptimizeNonFlushPacketSending extends ConfigModules { 通过使用 Netty 的 lazyExecute 方法来优化非刷新数据包的发送, 避免在调度数据包操作时进行昂贵的线程唤醒调用. - 需要重启服务器才能生效. - """)); + 需要重启服务器才能生效.""")); } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java index 903f1776..4e3323a8 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/DontSaveEntity.java @@ -15,8 +15,7 @@ public class DontSaveEntity extends ConfigModules { @Override public void onLoaded() { dontSavePrimedTNT = config.getBoolean(getBasePath() + ".dont-save-primed-tnt", dontSavePrimedTNT, - config.pickStringRegionBased( - """ + config.pickStringRegionBased(""" Disable save primed tnt on chunk unloads. Useful for redstone/technical servers, can prevent machines from being exploded by TNT, when player disconnected caused by Internet issue.""", diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/map/BlockPosIterator.java b/leaf-server/src/main/java/org/dreeam/leaf/util/map/BlockPosIterator.java index bf269d54..b867cc3b 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/util/map/BlockPosIterator.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/map/BlockPosIterator.java @@ -11,6 +11,7 @@ import org.jspecify.annotations.Nullable; @NullMarked public final class BlockPosIterator extends AbstractIterator { + private final int startX; private final int startY; private final int startZ; diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/map/spottedleaf/LeafConcurrentLong2ReferenceChainedHashTable.java b/leaf-server/src/main/java/org/dreeam/leaf/util/map/spottedleaf/LeafConcurrentLong2ReferenceChainedHashTable.java index 2e6719b8..27698be1 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/util/map/spottedleaf/LeafConcurrentLong2ReferenceChainedHashTable.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/map/spottedleaf/LeafConcurrentLong2ReferenceChainedHashTable.java @@ -8,12 +8,18 @@ import ca.spottedleaf.concurrentutil.util.ThrowUtil; import ca.spottedleaf.concurrentutil.util.Validate; import java.lang.invoke.VarHandle; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.PrimitiveIterator; +import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.LongAdder; import java.util.function.BiFunction; import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.LongConsumer; import java.util.function.LongFunction; import java.util.function.Predicate; @@ -47,8 +53,9 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable protected static final int DEFAULT_CAPACITY = 16; protected static final float DEFAULT_LOAD_FACTOR = 0.75f; - /** The maximum capacity, used if a higher value is implicitly specified by either - * of the constructors with arguments. MUST be a power of two <= 1<<30. + /** + * The maximum capacity, used if a higher value is implicitly specified by either + * of the constructors with arguments. MUST be a power of two <= 1<<30. */ protected static final int MAXIMUM_CAPACITY = 1 << 30; // 2^30 @@ -57,13 +64,19 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable // --- Instance Fields --- - /** Tracks the number of mappings, using LongAdder for better high-contention performance. */ + /** + * Tracks the number of mappings, using LongAdder for better high-contention performance. + */ protected final LongAdder size = new LongAdder(); - /** The load factor for the hash table. */ + /** + * The load factor for the hash table. + */ protected final float loadFactor; - /** The hash table array. Elements are accessed using VarHandles. */ + /** + * The hash table array. Elements are accessed using VarHandles. + */ protected volatile TableEntry[] table; /** @@ -75,6 +88,7 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable // --- VarHandles --- protected static final VarHandle THRESHOLD_HANDLE; + static { try { THRESHOLD_HANDLE = ConcurrentUtil.getVarHandle(LeafConcurrentLong2ReferenceChainedHashTable.class, "threshold", int.class); @@ -135,7 +149,7 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable /** * Creates a new, empty map with the specified initial capacity and load factor. * - * @param capacity The initial capacity. + * @param capacity The initial capacity. * @param loadFactor The load factor threshold. * @throws IllegalArgumentException if the initial capacity is negative or the load factor is non-positive/NaN. */ @@ -158,7 +172,7 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable * Creates a new, empty map with an initial capacity sufficient to hold the specified number of elements * without resizing, using the specified load factor. * - * @param expected The expected number of elements. + * @param expected The expected number of elements. * @param loadFactor The load factor threshold. * @throws IllegalArgumentException if the expected size is negative or the load factor is non-positive/NaN. */ @@ -176,7 +190,9 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable // --- Internal Helper Methods --- - /** Calculates the target resize threshold. */ + /** + * Calculates the target resize threshold. + */ protected static int getTargetThreshold(final int capacity, final float loadFactor) { if (capacity >= MAXIMUM_CAPACITY) { return THRESHOLD_NO_RESIZE; // Max capacity reached, no more resizing @@ -191,7 +207,9 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable } - /** Calculates the power-of-two capacity for a given initial capacity request. */ + /** + * Calculates the power-of-two capacity for a given initial capacity request. + */ protected static int getCapacityFor(final int requestedCapacity) { if (requestedCapacity <= 0) { // Default capacity if non-positive requested, could also throw exception @@ -204,12 +222,16 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable return IntegerUtil.roundCeilLog2(Math.max(DEFAULT_CAPACITY, requestedCapacity)); } - /** Computes the hash code for the key. Uses mixing to spread keys more evenly. */ + /** + * Computes the hash code for the key. Uses mixing to spread keys more evenly. + */ protected static int getHash(final long key) { return (int) HashUtil.mix(key); // Assumes HashUtil.mix provides good distribution } - /** Returns the load factor associated with this map. */ + /** + * Returns the load factor associated with this map. + */ public final float getLoadFactor() { return this.loadFactor; } @@ -269,7 +291,6 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable * Returns null if the key is not found. * The returned node's value might be null if it's a placeholder during a compute operation. */ - @SuppressWarnings("unchecked") protected final TableEntry getNode(final long key) { final int hash = getHash(key); TableEntry[] currentTable = this.table; // Volatile read @@ -364,7 +385,7 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable * Returns the value to which the specified key is mapped, or * {@code defaultValue} if this map contains no mapping for the key. * - * @param key the key whose associated value is to be returned + * @param key the key whose associated value is to be returned * @param defaultValue the default mapping of the key * @return the value mapped to the key, or {@code defaultValue} if none */ @@ -405,7 +426,7 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable TableEntry node; while ((node = iterator.findNext()) != null) { // findNext safely iterates through nodes V nodeValue = node.getValueVolatile(); // Volatile read for visibility - if (nodeValue != null && value.equals(nodeValue)) { + if (value.equals(nodeValue)) { return true; } } @@ -541,10 +562,12 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable int hash = getHash(current.key); if ((hash & oldCapacity) == 0) { // Low bin (index i) - if (lowT == null) lowH = current; else lowT.setNextPlain(current); + if (lowT == null) lowH = current; + else lowT.setNextPlain(current); lowT = current; } else { // High bin (index i + oldCapacity) - if (highT == null) highH = current; else highT.setNextPlain(current); + if (highT == null) highH = current; + else highT.setNextPlain(current); highT = current; } current = next; @@ -575,20 +598,24 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with {@code key}, or - * {@code null} if there was no mapping for {@code key}. + * {@code null} if there was no mapping for {@code key}. * @throws NullPointerException if the specified value is null */ public V put(final long key, final V value) { Validate.notNull(value, "Value may not be null"); final int hash = getHash(key); - int sizeDelta = 0; - V oldValue = null; + int sizeDelta; + V oldValue; TableEntry[] currentTable = this.table; table_loop: for (;;) { final int tableLength = currentTable.length; - if (tableLength == 0) { currentTable = this.table; if (currentTable.length == 0) continue; } // Init check + // Init check + if (tableLength == 0) { + currentTable = this.table; + if (currentTable.length == 0) continue; + } final int index = hash & (tableLength - 1); TableEntry head = getAtIndexVolatile(currentTable, index); @@ -653,8 +680,7 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable sizeDelta = 1; oldValue = null; } else { - // Should not happen if head was non-null/non-marker. Retry. - continue table_loop; + continue table_loop; // Should not happen if head was non-null/non-marker. Retry. } } // End synchronized break table_loop; // Operation completed within lock @@ -674,20 +700,23 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with the specified key, or - * {@code null} if there was no mapping for the key. + * {@code null} if there was no mapping for the key. * @throws NullPointerException if the specified value is null */ public V putIfAbsent(final long key, final V value) { Validate.notNull(value, "Value may not be null"); final int hash = getHash(key); int sizeDelta = 0; - V existingValue = null; + V existingValue; TableEntry[] currentTable = this.table; table_loop: - for(;;) { + for (;;) { final int tableLength = currentTable.length; - if (tableLength == 0) { currentTable = this.table; continue; } + if (tableLength == 0) { + currentTable = this.table; + continue; + } final int index = hash & (tableLength - 1); TableEntry head = getAtIndexVolatile(currentTable, index); @@ -710,7 +739,7 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable // Case 3: Lock-free check (optimistic) TableEntry node = head; - while(node != null) { + while (node != null) { if (node.key == key) { existingValue = node.getValueVolatile(); // Volatile read if (existingValue != null) { @@ -774,17 +803,17 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable * @param key key with which the specified value is associated * @param value value to be associated with the specified key * @return the previous value associated with the specified key, or - * {@code null} if there was no mapping for the key. + * {@code null} if there was no mapping for the key. * @throws NullPointerException if the specified value is null */ public V replace(final long key, final V value) { Validate.notNull(value, "Value may not be null"); final int hash = getHash(key); - V oldValue = null; + V oldValue; TableEntry[] currentTable = this.table; table_loop: - for(;;) { + for (;;) { final int tableLength = currentTable.length; if (tableLength == 0) return null; @@ -859,7 +888,7 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable TableEntry[] currentTable = this.table; table_loop: - for(;;) { + for (;;) { final int tableLength = currentTable.length; if (tableLength == 0) return false; @@ -922,7 +951,7 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable * * @param key key whose mapping is to be removed from the map * @return the previous value associated with {@code key}, or - * {@code null} if there was no mapping for {@code key} + * {@code null} if there was no mapping for {@code key} */ public V remove(final long key) { final int hash = getHash(key); @@ -931,7 +960,7 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable TableEntry[] currentTable = this.table; table_loop: - for(;;) { + for (;;) { final int tableLength = currentTable.length; if (tableLength == 0) return null; @@ -986,7 +1015,7 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable /** * Removes the entry for a key only if currently mapped to a given value. * - * @param key key with which the specified value is associated + * @param key key with which the specified value is associated * @param expect value expected to be associated with the specified key * @return {@code true} if the value was removed */ @@ -997,7 +1026,7 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable TableEntry[] currentTable = this.table; table_loop: - for(;;) { + for (;;) { final int tableLength = currentTable.length; if (tableLength == 0) return false; @@ -1056,10 +1085,10 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable /** * Removes the entry for the specified key only if its value satisfies the given predicate. * - * @param key key whose mapping is to be removed from the map + * @param key key whose mapping is to be removed from the map * @param predicate the predicate to apply to the value associated with the key * @return the value associated with the key before removal if the predicate was satisfied and the entry was removed, - * otherwise {@code null}. + * otherwise {@code null}. * @throws NullPointerException if the specified predicate is null */ public V removeIf(final long key, final Predicate predicate) { @@ -1071,7 +1100,7 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable TableEntry[] currentTable = this.table; table_loop: - for(;;) { + for (;;) { final int tableLength = currentTable.length; if (tableLength == 0) return null; @@ -1134,7 +1163,7 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable * (or {@code null} if there is no current mapping). The function is * applied atomically. * - * @param key key with which the specified value is to be associated + * @param key key with which the specified value is to be associated * @param function the function to compute a value * @return the new value associated with the specified key, or null if none * @throws NullPointerException if the specified function is null @@ -1143,13 +1172,16 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable Validate.notNull(function, "Function cannot be null"); final int hash = getHash(key); int sizeDelta = 0; - V finalValue = null; + V finalValue; TableEntry[] currentTable = this.table; table_loop: - for(;;) { + for (;;) { final int tableLength = currentTable.length; - if (tableLength == 0) { currentTable = this.table; continue; } + if (tableLength == 0) { + currentTable = this.table; + continue; + } final int index = hash & (tableLength - 1); TableEntry head = getAtIndexVolatile(currentTable, index); @@ -1162,7 +1194,10 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable if (getAtIndexVolatile(currentTable, index) == null) { // Re-check bin try { computedValue = function.apply(key, null); // Compute with null old value - } catch (Throwable t) { ThrowUtil.throwUnchecked(t); return null; } + } catch (Throwable t) { + ThrowUtil.throwUnchecked(t); + return null; + } if (computedValue != null) { placeholder.setValuePlain(computedValue); // Set value before CAS @@ -1205,7 +1240,10 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable V computedValue; try { computedValue = function.apply(key, oldValue); - } catch (Throwable t) { ThrowUtil.throwUnchecked(t); return null; } + } catch (Throwable t) { + ThrowUtil.throwUnchecked(t); + return null; + } if (computedValue != null) { node.setValueVolatile(computedValue); // Update value (volatile write) @@ -1229,7 +1267,10 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable V computedValue; try { computedValue = function.apply(key, null); - } catch (Throwable t) { ThrowUtil.throwUnchecked(t); return null; } + } catch (Throwable t) { + ThrowUtil.throwUnchecked(t); + return null; + } if (computedValue != null) { // Add new mapping @@ -1237,7 +1278,7 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable sizeDelta = 1; TableEntry newNode = new TableEntry<>(key, computedValue); if (prev != null) prev.setNextRelease(newNode); // Release write - else { continue table_loop; } // Should not happen + else continue table_loop; // Should not happen } else { finalValue = null; sizeDelta = 0; @@ -1257,23 +1298,26 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable * compute its value using the given mapping function and enters it into * this map unless {@code null}. * - * @param key key with which the specified value is to be associated + * @param key key with which the specified value is to be associated * @param function the function to compute a value * @return the current (existing or computed) value associated with the specified key, - * or null if the computed value is null + * or null if the computed value is null * @throws NullPointerException if the specified function is null */ public V computeIfAbsent(final long key, final LongFunction function) { Validate.notNull(function, "Function cannot be null"); final int hash = getHash(key); int sizeDelta = 0; - V finalValue = null; + V finalValue; TableEntry[] currentTable = this.table; table_loop: - for(;;) { + for (;;) { final int tableLength = currentTable.length; - if (tableLength == 0) { currentTable = this.table; continue; } + if (tableLength == 0) { + currentTable = this.table; + continue; + } final int index = hash & (tableLength - 1); TableEntry head = getAtIndexVolatile(currentTable, index); @@ -1286,7 +1330,10 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable if (getAtIndexVolatile(currentTable, index) == null) { try { computedValue = function.apply(key); - } catch (Throwable t) { ThrowUtil.throwUnchecked(t); return null; } + } catch (Throwable t) { + ThrowUtil.throwUnchecked(t); + return null; + } if (computedValue != null) { placeholder.setValuePlain(computedValue); @@ -1344,7 +1391,10 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable V computedValue; try { computedValue = function.apply(key); - } catch (Throwable t) { ThrowUtil.throwUnchecked(t); return null; } + } catch (Throwable t) { + ThrowUtil.throwUnchecked(t); + return null; + } if (computedValue != null) { node.setValueVolatile(computedValue); // Volatile write @@ -1364,14 +1414,17 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable V computedValue; try { computedValue = function.apply(key); - } catch (Throwable t) { ThrowUtil.throwUnchecked(t); return null; } + } catch (Throwable t) { + ThrowUtil.throwUnchecked(t); + return null; + } if (computedValue != null) { finalValue = computedValue; sizeDelta = 1; TableEntry newNode = new TableEntry<>(key, computedValue); if (prev != null) prev.setNextRelease(newNode); // Release write - else { continue table_loop; } // Should not happen + else continue table_loop; // Should not happen } else { finalValue = null; sizeDelta = 0; @@ -1389,7 +1442,7 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable * If the value for the specified key is present, attempts to compute a new * mapping given the key and its current mapped value. * - * @param key key with which the specified value is to be associated + * @param key key with which the specified value is to be associated * @param function the function to compute a value * @return the new value associated with the specified key, or null if none * @throws NullPointerException if the specified function is null @@ -1397,12 +1450,12 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable public V computeIfPresent(final long key, final BiLong1Function function) { Validate.notNull(function, "Function cannot be null"); final int hash = getHash(key); - int sizeDelta = 0; - V finalValue = null; + int sizeDelta; + V finalValue; TableEntry[] currentTable = this.table; table_loop: - for(;;) { + for (;;) { final int tableLength = currentTable.length; if (tableLength == 0) return null; @@ -1432,7 +1485,10 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable V computedValue; try { computedValue = function.apply(key, oldValue); - } catch (Throwable t) { ThrowUtil.throwUnchecked(t); return null; } + } catch (Throwable t) { + ThrowUtil.throwUnchecked(t); + return null; + } if (computedValue != null) { node.setValueVolatile(computedValue); // Update (volatile write) @@ -1474,25 +1530,28 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable * Otherwise, replaces the associated value with the results of the given * remapping function, or removes if the result is {@code null}. * - * @param key key with which the resulting value is to be associated - * @param value the non-null value to be merged with the existing value + * @param key key with which the resulting value is to be associated + * @param value the non-null value to be merged with the existing value * @param function the function to recompute a value if present * @return the new value associated with the specified key, or null if no - * value is associated with the key + * value is associated with the key * @throws NullPointerException if the specified value or function is null */ public V merge(final long key, final V value, final BiFunction function) { Validate.notNull(value, "Value cannot be null"); Validate.notNull(function, "Function cannot be null"); final int hash = getHash(key); - int sizeDelta = 0; - V finalValue = null; + int sizeDelta; + V finalValue; TableEntry[] currentTable = this.table; table_loop: - for(;;) { + for (;;) { final int tableLength = currentTable.length; - if (tableLength == 0) { currentTable = this.table; continue; } + if (tableLength == 0) { + currentTable = this.table; + continue; + } final int index = hash & (tableLength - 1); TableEntry head = getAtIndexVolatile(currentTable, index); @@ -1531,7 +1590,10 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable if (oldValue != null) { try { computedValue = function.apply(oldValue, value); // Apply function - } catch (Throwable t) { ThrowUtil.throwUnchecked(t); return null; } + } catch (Throwable t) { + ThrowUtil.throwUnchecked(t); + return null; + } } else { computedValue = value; // Use provided value if old was placeholder } @@ -1559,7 +1621,7 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable sizeDelta = 1; TableEntry newNode = new TableEntry<>(key, value); if (prev != null) prev.setNextRelease(newNode); // Release write - else { continue table_loop; } // Should not happen + else continue table_loop; // Should not happen break table_loop; // Done } // End synchronized(head) } // End table_loop @@ -1612,17 +1674,34 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable // --- Iterators and Views --- - /** Returns an iterator over the map entries. */ - public Iterator> entryIterator() { return new EntryIterator<>(this); } + /** + * Returns an iterator over the map entries. + */ + public Iterator> entryIterator() { + return new EntryIterator<>(this); + } - /** Returns an iterator over the map entries (implements Iterable). */ - @Override public final Iterator> iterator() { return this.entryIterator(); } + /** + * Returns an iterator over the map entries (implements Iterable). + */ + @Override + public final Iterator> iterator() { + return this.entryIterator(); + } - /** Returns an iterator over the keys. */ - public PrimitiveIterator.OfLong keyIterator() { return new KeyIterator<>(this); } + /** + * Returns an iterator over the keys. + */ + public PrimitiveIterator.OfLong keyIterator() { + return new KeyIterator<>(this); + } - /** Returns an iterator over the values. */ - public Iterator valueIterator() { return new ValueIterator<>(this); } + /** + * Returns an iterator over the values. + */ + public Iterator valueIterator() { + return new ValueIterator<>(this); + } /** * Returns a {@link Collection} view of the values contained in this map. @@ -1666,52 +1745,95 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable private volatile TableEntry next; private final boolean resizeMarker; - /** Constructor for regular map entries. */ + /** + * Constructor for regular map entries. + */ TableEntry(final long key, final V value) { this(key, value, false); } - /** Constructor for potentially creating resize markers. */ + /** + * Constructor for potentially creating resize markers. + */ TableEntry(final long key, final V value, final boolean resize) { this.key = key; this.resizeMarker = resize; this.setValuePlain(value); // Initial plain set } - public long getKey() { return this.key; } - public V getValue() { return getValueVolatile(); } + public long getKey() { + return this.key; + } + + public V getValue() { + return getValueVolatile(); + } public V setValue(V newValue) { throw new UnsupportedOperationException("Direct setValue on TableEntry is not supported; use map methods."); } - @SuppressWarnings("unchecked") final V getValuePlain() { return (V) VALUE_HANDLE.get(this); } - @SuppressWarnings("unchecked") final V getValueAcquire() { return (V) VALUE_HANDLE.getAcquire(this); } - @SuppressWarnings("unchecked") final V getValueVolatile() { return (V) VALUE_HANDLE.getVolatile(this); } - - final void setValuePlain(final V value) { VALUE_HANDLE.set(this, value); } - final void setValueRelease(final V value) { VALUE_HANDLE.setRelease(this, value); } - final void setValueVolatile(final V value) { VALUE_HANDLE.setVolatile(this, value); } + @SuppressWarnings("unchecked") + V getValuePlain() { + return (V) VALUE_HANDLE.get(this); + } @SuppressWarnings("unchecked") - final boolean compareAndSetValueVolatile(final V expect, final V update) { + V getValueAcquire() { + return (V) VALUE_HANDLE.getAcquire(this); + } + + @SuppressWarnings("unchecked") + V getValueVolatile() { + return (V) VALUE_HANDLE.getVolatile(this); + } + + void setValuePlain(final V value) { + VALUE_HANDLE.set(this, value); + } + + void setValueRelease(final V value) { + VALUE_HANDLE.setRelease(this, value); + } + + void setValueVolatile(final V value) { + VALUE_HANDLE.setVolatile(this, value); + } + + boolean compareAndSetValueVolatile(final V expect, final V update) { return VALUE_HANDLE.compareAndSet(this, expect, update); } - @SuppressWarnings("unchecked") final TableEntry getNextPlain() { return (TableEntry) NEXT_HANDLE.get(this); } - @SuppressWarnings("unchecked") final TableEntry getNextVolatile() { return (TableEntry) NEXT_HANDLE.getVolatile(this); } + @SuppressWarnings("unchecked") + TableEntry getNextPlain() { + return (TableEntry) NEXT_HANDLE.get(this); + } - final void setNextPlain(final TableEntry next) { NEXT_HANDLE.set(this, next); } - final void setNextRelease(final TableEntry next) { NEXT_HANDLE.setRelease(this, next); } - final void setNextVolatile(final TableEntry next) { NEXT_HANDLE.setVolatile(this, next); } + @SuppressWarnings("unchecked") + TableEntry getNextVolatile() { + return (TableEntry) NEXT_HANDLE.getVolatile(this); + } - final boolean isResizeMarker() { return this.resizeMarker; } + void setNextPlain(final TableEntry next) { + NEXT_HANDLE.set(this, next); + } + + void setNextRelease(final TableEntry next) { + NEXT_HANDLE.setRelease(this, next); + } + + void setNextVolatile(final TableEntry next) { + NEXT_HANDLE.setVolatile(this, next); + } + + boolean isResizeMarker() { + return this.resizeMarker; + } @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || !(o instanceof LeafConcurrentLong2ReferenceChainedHashTable.TableEntry)) return false; - TableEntry that = (TableEntry) o; + if (!(o instanceof TableEntry that)) return false; return key == that.key && Objects.equals(getValueVolatile(), that.getValueVolatile()); // Use volatile read for value } @@ -1824,7 +1946,9 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable return this.nextNode != null; } - /** Internal method to get the next node and advance. */ + /** + * Internal method to get the next node and advance. + */ final TableEntry findNext() { TableEntry e = this.nextNode; if (e == null) { @@ -1847,7 +1971,9 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable this.lastReturned = null; } - /** Gets the next node, updates lastReturned, advances iterator. */ + /** + * Gets the next node, updates lastReturned, advances iterator. + */ protected final TableEntry nextNode() throws NoSuchElementException { TableEntry node = this.nextNode; // Node pre-fetched by advance() if (node == null) { @@ -1880,35 +2006,48 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable } } - /** Iterator over map entries (TableEntry objects). */ + /** + * Iterator over map entries (TableEntry objects). + */ protected static final class EntryIterator extends BaseIteratorImpl> { - EntryIterator(final LeafConcurrentLong2ReferenceChainedHashTable map) { super(map); } + EntryIterator(final LeafConcurrentLong2ReferenceChainedHashTable map) { + super(map); + } - @Override public TableEntry next() throws NoSuchElementException { + @Override + public TableEntry next() throws NoSuchElementException { return nextNode(); } } - /** Iterator over map keys (long primitives). */ + /** + * Iterator over map keys (long primitives). + */ protected static final class KeyIterator extends BaseIteratorImpl implements PrimitiveIterator.OfLong { - KeyIterator(final LeafConcurrentLong2ReferenceChainedHashTable map) { super(map); } + KeyIterator(final LeafConcurrentLong2ReferenceChainedHashTable map) { + super(map); + } - @Override public long nextLong() throws NoSuchElementException { + @Override + public long nextLong() throws NoSuchElementException { return nextNode().key; } - @Override public Long next() throws NoSuchElementException { + @Override + public Long next() throws NoSuchElementException { return nextLong(); // Autoboxing } - @Override public void forEachRemaining(final LongConsumer action) { + @Override + public void forEachRemaining(final LongConsumer action) { Validate.notNull(action, "Action may not be null"); while (hasNext()) { action.accept(nextLong()); } } - @Override public void forEachRemaining(final Consumer action) { + @Override + public void forEachRemaining(final Consumer action) { if (action instanceof LongConsumer) { forEachRemaining((LongConsumer) action); } else { @@ -1920,18 +2059,25 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable } } - /** Iterator over map values. */ + /** + * Iterator over map values. + */ protected static final class ValueIterator extends BaseIteratorImpl { - ValueIterator(final LeafConcurrentLong2ReferenceChainedHashTable map) { super(map); } + ValueIterator(final LeafConcurrentLong2ReferenceChainedHashTable map) { + super(map); + } - @Override public V next() throws NoSuchElementException { + @Override + public V next() throws NoSuchElementException { return nextNode().getValueVolatile(); // Volatile read for value } } // --- Collection Views --- - /** Base class for Collection views (Values, EntrySet). */ + /** + * Base class for Collection views (Values, EntrySet). + */ protected static abstract class BaseCollection implements Collection { protected final LeafConcurrentLong2ReferenceChainedHashTable map; @@ -1939,11 +2085,21 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable this.map = Validate.notNull(map); } - @Override public int size() { return map.size(); } - @Override public boolean isEmpty() { return map.isEmpty(); } - @Override public abstract boolean contains(Object o); // Subclass responsibility + @Override + public int size() { + return map.size(); + } - @Override public boolean containsAll(Collection c) { + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public abstract boolean contains(Object o); // Subclass responsibility + + @Override + public boolean containsAll(Collection c) { Validate.notNull(c); for (Object e : c) { if (!contains(e)) return false; @@ -1951,13 +2107,14 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable return true; } - @Override public Object[] toArray() { + @Override + public Object[] toArray() { List list = new ArrayList<>(map.size()); for (E e : this) list.add(e); // Uses iterator() from subclass return list.toArray(); } - @Override @SuppressWarnings("unchecked") + @Override public T[] toArray(T[] a) { Validate.notNull(a); List list = new ArrayList<>(map.size()); @@ -1965,11 +2122,23 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable return list.toArray(a); } - @Override public void clear() { map.clear(); } - @Override public boolean add(E e) { throw new UnsupportedOperationException(); } - @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } + @Override + public void clear() { + map.clear(); + } - @Override public boolean remove(Object o) { + @Override + public boolean add(E e) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { Iterator it = iterator(); // Subclass provides iterator while (it.hasNext()) { if (Objects.equals(o, it.next())) { @@ -1980,7 +2149,8 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable return false; } - @Override public boolean removeAll(Collection c) { + @Override + public boolean removeAll(Collection c) { Validate.notNull(c); boolean modified = false; Iterator it = iterator(); @@ -1993,7 +2163,8 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable return modified; } - @Override public boolean retainAll(Collection c) { + @Override + public boolean retainAll(Collection c) { Validate.notNull(c); boolean modified = false; Iterator it = iterator(); @@ -2006,7 +2177,8 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable return modified; } - @Override public boolean removeIf(Predicate filter) { + @Override + public boolean removeIf(Predicate filter) { Validate.notNull(filter); boolean removed = false; Iterator it = iterator(); @@ -2019,19 +2191,21 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable return removed; } - @Override public String toString() { + @Override + public String toString() { Iterator it = iterator(); - if (! it.hasNext()) return "[]"; + if (!it.hasNext()) return "[]"; StringBuilder sb = new StringBuilder("["); for (;;) { E e = it.next(); sb.append(e == this ? "(this Collection)" : e); - if (! it.hasNext()) return sb.append(']').toString(); + if (!it.hasNext()) return sb.append(']').toString(); sb.append(',').append(' '); } } - @Override public void forEach(Consumer action) { + @Override + public void forEach(Consumer action) { Validate.notNull(action); for (E e : this) { // Uses iterator() from subclass action.accept(e); @@ -2039,46 +2213,64 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable } } - /** Collection view for the map's values. */ + /** + * Collection view for the map's values. + */ protected static final class Values extends BaseCollection { - Values(LeafConcurrentLong2ReferenceChainedHashTable map) { super(map); } - - @Override public boolean contains(Object o) { - try { - return o != null && map.containsValue((V)o); - } catch (ClassCastException cce) { return false; } + Values(LeafConcurrentLong2ReferenceChainedHashTable map) { + super(map); } - @Override public Iterator iterator() { return map.valueIterator(); } + @Override + public boolean contains(Object o) { + try { + return o != null && map.containsValue((V) o); + } catch (ClassCastException cce) { + return false; + } + } + + @Override + public Iterator iterator() { + return map.valueIterator(); + } } - /** Set view for the map's entries (TableEntry objects). */ + /** + * Set view for the map's entries (TableEntry objects). + */ protected static final class EntrySet extends BaseCollection> implements Set> { - EntrySet(LeafConcurrentLong2ReferenceChainedHashTable map) { super(map); } + EntrySet(LeafConcurrentLong2ReferenceChainedHashTable map) { + super(map); + } - @Override public boolean contains(Object o) { - if (!(o instanceof LeafConcurrentLong2ReferenceChainedHashTable.TableEntry)) return false; - TableEntry entry = (TableEntry) o; + @Override + public boolean contains(Object o) { + if (!(o instanceof LeafConcurrentLong2ReferenceChainedHashTable.TableEntry entry)) return false; V mappedValue = map.get(entry.getKey()); // Concurrent read // Use volatile read on entry's value for consistent comparison return mappedValue != null && Objects.equals(mappedValue, entry.getValueVolatile()); } - @Override public Iterator> iterator() { return map.entryIterator(); } + @Override + public Iterator> iterator() { + return map.entryIterator(); + } - @Override public boolean remove(Object o) { - if (!(o instanceof LeafConcurrentLong2ReferenceChainedHashTable.TableEntry)) return false; - TableEntry entry = (TableEntry) o; + @Override + public boolean remove(Object o) { + if (!(o instanceof LeafConcurrentLong2ReferenceChainedHashTable.TableEntry entry)) return false; try { // Use map's atomic remove(key, value) // Use volatile read for the expected value - return map.remove(entry.getKey(), (V)entry.getValueVolatile()); - } catch(ClassCastException | NullPointerException cce) { // Handle potential type/null issues + return map.remove(entry.getKey(), (V) entry.getValueVolatile()); + } catch (ClassCastException | NullPointerException cce) { // Handle potential type/null issues return false; } } - @Override public int hashCode() { + @Override + public int hashCode() { int h = 0; for (TableEntry e : this) { h += e.hashCode(); // Uses entry's hashCode @@ -2086,10 +2278,10 @@ public class LeafConcurrentLong2ReferenceChainedHashTable implements Iterable return h; } - @Override public boolean equals(Object o) { + @Override + public boolean equals(Object o) { if (o == this) return true; - if (!(o instanceof Set)) return false; - Set c = (Set) o; + if (!(o instanceof Set c)) return false; if (c.size() != size()) return false; try { // relies on containsAll checking entry equality correctly diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/queue/SpscIntQueue.java b/leaf-server/src/main/java/org/dreeam/leaf/util/queue/SpscIntQueue.java index 9afc72df..3368c34e 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/util/queue/SpscIntQueue.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/queue/SpscIntQueue.java @@ -2,6 +2,7 @@ package org.dreeam.leaf.util.queue; /// Lock-free Single Producer Single Consumer Queue public class SpscIntQueue { + private final int[] data; private final PaddedAtomicInteger producerIdx = new PaddedAtomicInteger(); private final PaddedAtomicInteger producerCachedIdx = new PaddedAtomicInteger();