diff --git a/.github/workflows/build-1215.yml b/.github/workflows/build-1215.yml
index 966c5820..1a172f97 100644
--- a/.github/workflows/build-1215.yml
+++ b/.github/workflows/build-1215.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.5-R0.1-SNAPSHOT-mojmap.jar ./leaf-1.21.5-${{ env.BUILD_NUMBER }}.jar
diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml
index e360b185..5b536e73 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 }}
diff --git a/README.md b/README.md
index df6d32a0..8156cd5e 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
[](https://github.com/Winds-Studio/Leaf/releases)⠀
[](https://github.com/Winds-Studio/Leaf/actions)⠀
[](https://discord.gg/gfgAwdSEuM)
-[](https://docs.leafmc.one)
+[](https://www.leafmc.one/docs/)
**Leaf** is a [Paper](https://papermc.io/) fork designed to be customized and high-performance.
@@ -42,7 +42,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: [www.leafmc.one/docs](https://www.leafmc.one/docs/)
+Documentation on how to use/configure Leaf: [www.leafmc.one/docs](https://www.leafmc.one/docs)
## 📦 Building
Building a Paperclip JAR for distribution:
@@ -118,6 +118,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/build-data/leaf.at b/build-data/leaf.at
index 16bc187c..329dbff8 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.server.players.PlayerList SEND_PLAYER_INFO_INTERVAL
public net.minecraft.util.Mth SIN
diff --git a/leaf-api/build.gradle.kts.patch b/leaf-api/build.gradle.kts.patch
index 222f6bbe..f2a00386 100644
--- a/leaf-api/build.gradle.kts.patch
+++ b/leaf-api/build.gradle.kts.patch
@@ -13,16 +13,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") {
diff --git a/leaf-api/paper-patches/features/0019-PlayerInventoryOverflowEvent.patch b/leaf-api/paper-patches/features/0019-PlayerInventoryOverflowEvent.patch
index fb05b9c6..155e82c3 100644
--- a/leaf-api/paper-patches/features/0019-PlayerInventoryOverflowEvent.patch
+++ b/leaf-api/paper-patches/features/0019-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 ef9d9f3ddaf25aa990715139e1916672f76c35e5..97b591ab30d6e0af4518b71fefcf9c8f3c050176 100644
+--- a/src/main/java/org/bukkit/event/HandlerList.java
++++ b/src/main/java/org/bukkit/event/HandlerList.java
+@@ -64,6 +64,7 @@ public class HandlerList {
+ h.handlers = null;
+ }
+ }
++ org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent.isListeningInvOverflowCached = -1; // Leaf - PlayerInventoryOverflowEvent
+ }
+ }
+
+@@ -77,6 +78,7 @@ public class HandlerList {
+ for (HandlerList h : allLists) {
+ h.unregister(plugin);
+ }
++ org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent.isListeningInvOverflowCached = -1; // Leaf - PlayerInventoryOverflowEvent
+ }
+ }
+
+@@ -90,6 +92,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-archived-patches/removed/hardfork/server/1160-PaperPR-Fix-save-load-NaN-Entity-Motion.patch b/leaf-archived-patches/removed/hardfork/server/1160-PaperPR-Fix-save-load-NaN-Entity-Motion.patch
new file mode 100644
index 00000000..7f9f00da
--- /dev/null
+++ b/leaf-archived-patches/removed/hardfork/server/1160-PaperPR-Fix-save-load-NaN-Entity-Motion.patch
@@ -0,0 +1,40 @@
+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
+
+Removed since Paper 1.21.5, added on Paper
+
+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-archived-patches/removed/hardfork/server/1161-PaperPR-Fix-unnecessary-map-data-saves.patch b/leaf-archived-patches/removed/hardfork/server/1161-PaperPR-Fix-unnecessary-map-data-saves.patch
new file mode 100644
index 00000000..614b0da1
--- /dev/null
+++ b/leaf-archived-patches/removed/hardfork/server/1161-PaperPR-Fix-unnecessary-map-data-saves.patch
@@ -0,0 +1,47 @@
+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
+
+Removed since Paper 1.21.5, added on Paper
+
+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-archived-patches/unapplied/server/minecraft-patches/features/0138-SparklyPaper-Parallel-world-ticking.patch b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch
similarity index 98%
rename from leaf-archived-patches/unapplied/server/minecraft-patches/features/0138-SparklyPaper-Parallel-world-ticking.patch
rename to leaf-archived-patches/unapplied/server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch
index 698bc071..35e26162 100644
--- a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0138-SparklyPaper-Parallel-world-ticking.patch
+++ b/leaf-archived-patches/unapplied/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;
@@ -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) {
@@ -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..334a47659ba75fade062bc79df3731d1e449114b 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,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,6 +760,11 @@ index 9bc978ca290ca772b0367e89b69fe16b502b0cd2..17ff57db6f90b3f4facef691ad4347e6
+ // TCRF SparklyPaper (Pathothingi) start - parallel world ticking
+ java.util.function.Consumer portalEntityTask = entity -> {
+ assert entity.portalProcess != null;
++ // 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) {
++ return;
++ }
+
+ if (entity.portalProcess.isParallelCancelledByPlugin()) {
+ entity.portalProcess = null;
@@ -775,8 +780,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 +794,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 +3933,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
private Entity teleportCrossDimension(ServerLevel level, TeleportTransition teleportTransition) {
diff --git a/leaf-archived-patches/unapplied/server/minecraft-patches/features/1164-Sakura-Optimise-check-inside-blocks-and-traverse-blo.patch b/leaf-archived-patches/unapplied/server/minecraft-patches/features/1164-Sakura-Optimise-check-inside-blocks-and-traverse-blo.patch
new file mode 100644
index 00000000..76f2234d
--- /dev/null
+++ b/leaf-archived-patches/unapplied/server/minecraft-patches/features/1164-Sakura-Optimise-check-inside-blocks-and-traverse-blo.patch
@@ -0,0 +1,69 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Samsuik
+Date: Thu, 1 May 2025 22:38:48 +0200
+Subject: [PATCH] Sakura: Optimise-check-inside-blocks-and-traverse-blocks
+
+Dreeam TODO: refactor checkinsideblcoks
+
+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-archived-patches/unapplied/server/paper-patches/features/0049-SparklyPaper-Parallel-world-ticking.patch b/leaf-archived-patches/unapplied/server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch
similarity index 89%
rename from leaf-archived-patches/unapplied/server/paper-patches/features/0049-SparklyPaper-Parallel-world-ticking.patch
rename to leaf-archived-patches/unapplied/server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch
index ad38c547..2defa582 100644
--- a/leaf-archived-patches/unapplied/server/paper-patches/features/0049-SparklyPaper-Parallel-world-ticking.patch
+++ b/leaf-archived-patches/unapplied/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 196fddeab452e7bc89ef6758635e1d07074e7416..55a2ffa0fff6ef66b9bd5069300c09e9e3535c0d 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
@@ -263,10 +255,10 @@ index 196fddeab452e7bc89ef6758635e1d07074e7416..55a2ffa0fff6ef66b9bd5069300c09e9
} else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) {
// Leaf start - Multithreaded tracker
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 6bcece7ceb5be047371faf7ab85b3688ed3e045b..96faa72abe918e7ab3f1dec44072a5fee27b6476 100644
+index af33cab59932f4ec135caf94dc5828930833daf6..92463ddc6fdcf542ce4a6d2a5059d4a9f7a6085a 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-@@ -456,7 +456,12 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+@@ -455,7 +455,12 @@ public class CraftWorld extends CraftRegionAccessor implements World {
}
private boolean unloadChunk0(int x, int z, boolean save) {
@@ -280,7 +272,7 @@ index 6bcece7ceb5be047371faf7ab85b3688ed3e045b..96faa72abe918e7ab3f1dec44072a5fe
if (!this.isChunkLoaded(x, z)) {
return true;
}
-@@ -473,6 +478,8 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+@@ -472,6 +477,8 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public boolean refreshChunk(int x, int z) {
@@ -289,7 +281,7 @@ index 6bcece7ceb5be047371faf7ab85b3688ed3e045b..96faa72abe918e7ab3f1dec44072a5fe
ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
if (playerChunk == null) return false;
-@@ -523,7 +530,12 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+@@ -522,7 +529,12 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public boolean loadChunk(int x, int z, boolean generate) {
@@ -303,7 +295,7 @@ index 6bcece7ceb5be047371faf7ab85b3688ed3e045b..96faa72abe918e7ab3f1dec44072a5fe
warnUnsafeChunk("loading a faraway chunk", x, z); // Paper
ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper
-@@ -751,6 +763,8 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+@@ -750,6 +762,8 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) {
@@ -312,7 +304,7 @@ index 6bcece7ceb5be047371faf7ab85b3688ed3e045b..96faa72abe918e7ab3f1dec44072a5fe
this.world.captureTreeGeneration = true;
this.world.captureBlockStates = true;
boolean grownTree = this.generateTree(loc, type);
-@@ -866,6 +880,8 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+@@ -865,6 +879,8 @@ public class CraftWorld extends CraftRegionAccessor implements World {
}
public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source, Consumer configurator) {
// Paper end - expand explosion API
@@ -321,7 +313,7 @@ index 6bcece7ceb5be047371faf7ab85b3688ed3e045b..96faa72abe918e7ab3f1dec44072a5fe
net.minecraft.world.level.Level.ExplosionInteraction explosionType;
if (!breakBlocks) {
explosionType = net.minecraft.world.level.Level.ExplosionInteraction.NONE; // Don't break blocks
-@@ -957,6 +973,8 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+@@ -956,6 +972,8 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) {
@@ -330,7 +322,7 @@ index 6bcece7ceb5be047371faf7ab85b3688ed3e045b..96faa72abe918e7ab3f1dec44072a5fe
warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper
// Transient load for this tick
return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z);
-@@ -987,6 +1005,8 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+@@ -986,6 +1004,8 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public void setBiome(int x, int y, int z, Holder bb) {
BlockPos pos = new BlockPos(x, 0, z);
@@ -339,7 +331,7 @@ index 6bcece7ceb5be047371faf7ab85b3688ed3e045b..96faa72abe918e7ab3f1dec44072a5fe
if (this.world.hasChunkAt(pos)) {
net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkAt(pos);
-@@ -2329,6 +2349,8 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+@@ -2328,6 +2348,8 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) {
@@ -349,7 +341,7 @@ index 6bcece7ceb5be047371faf7ab85b3688ed3e045b..96faa72abe918e7ab3f1dec44072a5fe
}
// 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 5f04b2dcff7c0e967647bde9dfa0f35f59e8524c..8fa769f1f18d05b6f037c84175dcb56bead15b24 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 {
@@ -364,31 +356,32 @@ index 5f04b2dcff7c0e967647bde9dfa0f35f59e8524c..8fa769f1f18d05b6f037c84175dcb56b
return this.world.getBlockState(this.position);
}
-@@ -155,6 +160,11 @@ public class CraftBlock implements Block {
+@@ -157,6 +162,11 @@ public class CraftBlock implements Block {
}
- private void setData(final byte data, int flags) {
+ private void setData(final byte data, int flag) {
+ // SparklyPaper start - parallel world ticking
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable)
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
- this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flags);
+ this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flag);
}
-@@ -196,6 +206,11 @@ public class CraftBlock implements Block {
+@@ -198,6 +208,12 @@ public class CraftBlock implements Block {
}
- public static boolean setBlockState(LevelAccessor world, BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState, boolean applyPhysics) {
+ public static boolean setTypeAndData(LevelAccessor world, BlockPos position, net.minecraft.world.level.block.state.BlockState old, net.minecraft.world.level.block.state.BlockState blockData, boolean applyPhysics) {
+ // SparklyPaper start - parallel world ticking
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable)
-+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, pos, "Cannot modify world asynchronously");
++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
- // SPIGOT-611: need to do this to prevent glitchiness. Easier to handle this here (like /setblock) than to fix weirdness in block entity cleanup
- if (oldState.hasBlockEntity() && newState.getBlock() != oldState.getBlock()) { // SPIGOT-3725 remove old block entity if block changes
++
+ // SPIGOT-611: need to do this to prevent glitchiness. Easier to handle this here (like /setblock) than to fix weirdness in tile entity cleanup
+ if (old.hasBlockEntity() && blockData.getBlock() != old.getBlock()) { // SPIGOT-3725 remove old tile entity if block changes
// SPIGOT-4612: faster - just clear tile
-@@ -344,18 +359,33 @@ public class CraftBlock implements Block {
+@@ -343,18 +359,33 @@ public class CraftBlock implements Block {
@Override
public Biome getBiome() {
@@ -422,7 +415,7 @@ index 5f04b2dcff7c0e967647bde9dfa0f35f59e8524c..8fa769f1f18d05b6f037c84175dcb56b
this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio);
}
-@@ -376,6 +406,11 @@ public class CraftBlock implements Block {
+@@ -375,6 +406,11 @@ public class CraftBlock implements Block {
@Override
public boolean isBlockIndirectlyPowered() {
@@ -434,7 +427,7 @@ index 5f04b2dcff7c0e967647bde9dfa0f35f59e8524c..8fa769f1f18d05b6f037c84175dcb56b
return this.world.getMinecraftWorld().hasNeighborSignal(this.position);
}
-@@ -415,6 +450,11 @@ public class CraftBlock implements Block {
+@@ -414,6 +450,11 @@ public class CraftBlock implements Block {
@Override
public int getBlockPower(BlockFace face) {
@@ -446,7 +439,7 @@ index 5f04b2dcff7c0e967647bde9dfa0f35f59e8524c..8fa769f1f18d05b6f037c84175dcb56b
int power = 0;
net.minecraft.world.level.Level world = this.world.getMinecraftWorld();
int x = this.getX();
-@@ -483,6 +523,11 @@ public class CraftBlock implements Block {
+@@ -484,6 +525,11 @@ public class CraftBlock implements Block {
@Override
public boolean breakNaturally() {
@@ -458,7 +451,7 @@ index 5f04b2dcff7c0e967647bde9dfa0f35f59e8524c..8fa769f1f18d05b6f037c84175dcb56b
return this.breakNaturally(null);
}
-@@ -542,6 +587,11 @@ public class CraftBlock implements Block {
+@@ -543,6 +589,11 @@ public class CraftBlock implements Block {
@Override
public boolean applyBoneMeal(BlockFace face) {
@@ -470,18 +463,18 @@ index 5f04b2dcff7c0e967647bde9dfa0f35f59e8524c..8fa769f1f18d05b6f037c84175dcb56b
Direction direction = CraftBlock.blockFaceToNotch(face);
BlockFertilizeEvent event = null;
ServerLevel world = this.getCraftWorld().getHandle();
-@@ -553,8 +603,8 @@ public class CraftBlock implements Block {
+@@ -554,8 +605,8 @@ public class CraftBlock implements Block {
world.captureTreeGeneration = false;
- if (!world.capturedBlockStates.isEmpty()) {
+ if (world.capturedBlockStates.size() > 0) {
- TreeType treeType = SaplingBlock.treeType;
- SaplingBlock.treeType = null;
+ TreeType treeType = SaplingBlock.getTreeTypeRT(); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior)
+ SaplingBlock.setTreeTypeRT(null); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior)
- List states = new ArrayList<>(world.capturedBlockStates.values());
+ List blocks = new ArrayList<>(world.capturedBlockStates.values());
world.capturedBlockStates.clear();
StructureGrowEvent structureEvent = null;
-@@ -644,6 +694,11 @@ public class CraftBlock implements Block {
+@@ -644,6 +695,11 @@ public class CraftBlock implements Block {
@Override
public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) {
@@ -493,7 +486,7 @@ index 5f04b2dcff7c0e967647bde9dfa0f35f59e8524c..8fa769f1f18d05b6f037c84175dcb56b
Preconditions.checkArgument(start != null, "Location start cannot be null");
Preconditions.checkArgument(this.getWorld().equals(start.getWorld()), "Location start cannot be a different world");
start.checkFinite();
-@@ -685,6 +740,11 @@ public class CraftBlock implements Block {
+@@ -685,6 +741,11 @@ public class CraftBlock implements Block {
@Override
public boolean canPlace(BlockData data) {
@@ -505,7 +498,7 @@ index 5f04b2dcff7c0e967647bde9dfa0f35f59e8524c..8fa769f1f18d05b6f037c84175dcb56b
Preconditions.checkArgument(data != null, "BlockData cannot be null");
net.minecraft.world.level.block.state.BlockState iblockdata = ((CraftBlockData) data).getState();
net.minecraft.world.level.Level world = this.world.getMinecraftWorld();
-@@ -719,6 +779,11 @@ public class CraftBlock implements Block {
+@@ -719,6 +780,11 @@ public class CraftBlock implements Block {
@Override
public void tick() {
@@ -518,7 +511,7 @@ index 5f04b2dcff7c0e967647bde9dfa0f35f59e8524c..8fa769f1f18d05b6f037c84175dcb56b
this.getNMS().tick(level, this.position, level.random);
}
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
-index 3422970353dcd886934b9ee906467769d39abbde..13c91223bbb4841cab0e491037a36113a33faf15 100644
+index 768d3f93da2522d467183654260a8bd8653588b1..5cef786fa2e5dfd3e7b79918bc73af02891b0bea 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
@@ -26,6 +26,27 @@ public abstract class CraftBlockEntityState extends Craft
@@ -547,8 +540,8 @@ index 3422970353dcd886934b9ee906467769d39abbde..13c91223bbb4841cab0e491037a36113
+ }
+ // Leaf end - SparklyPaper - parallel world ticking mod
- public CraftBlockEntityState(World world, T blockEntity) {
- super(world, blockEntity.getBlockPos(), blockEntity.getBlockState());
+ public CraftBlockEntityState(World world, T tileEntity) {
+ super(world, tileEntity.getBlockPos(), tileEntity.getBlockState());
@@ -34,8 +55,8 @@ public abstract class CraftBlockEntityState extends Craft
try { // Paper - Show blockstate location if we failed to read it
@@ -557,14 +550,14 @@ index 3422970353dcd886934b9ee906467769d39abbde..13c91223bbb4841cab0e491037a36113
- if (DISABLE_SNAPSHOT) {
+ this.snapshotDisabled = getDisableSnapshotTL(); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior)
+ if (this.snapshotDisabled) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior)
- this.snapshot = this.blockEntity;
+ this.snapshot = this.tileEntity;
} else {
- this.snapshot = this.createSnapshot(blockEntity);
+ this.snapshot = this.createSnapshot(tileEntity);
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
-index c0a7659776a9f0fb82bb8563acbf3286b318fe03..69fc25b20cc84a2cd89cb0927aceba05df405e2a 100644
+index fa63a6cfcfcc4eee4503a82d85333c139c8c8b2b..d106f65e4b745242484a195958fc559268a7dee0 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
-@@ -217,6 +217,12 @@ public class CraftBlockState implements BlockState {
+@@ -215,6 +215,12 @@ public class CraftBlockState implements BlockState {
LevelAccessor access = this.getWorldHandle();
CraftBlock block = this.getBlock();
@@ -577,7 +570,7 @@ index c0a7659776a9f0fb82bb8563acbf3286b318fe03..69fc25b20cc84a2cd89cb0927aceba05
if (block.getType() != this.getType()) {
if (!force) {
return false;
-@@ -365,6 +371,8 @@ public class CraftBlockState implements BlockState {
+@@ -350,6 +356,8 @@ public class CraftBlockState implements BlockState {
@Override
public java.util.Collection getDrops(org.bukkit.inventory.ItemStack item, org.bukkit.entity.Entity entity) {
@@ -587,35 +580,37 @@ index c0a7659776a9f0fb82bb8563acbf3286b318fe03..69fc25b20cc84a2cd89cb0927aceba05
net.minecraft.world.item.ItemStack nms = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item);
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java
-index 321280d7c9c3c828cbf2eb19d2fd196a1f84d4c3..cd8f771e08cee5d00c53a8e70f0fe37cf393cd52 100644
+index 55572e799b5c8a74a546ac8febc14f80d5731c52..08a06c23c831a4de45b3e537228b837911019da8 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java
-@@ -195,14 +195,14 @@ public final class CraftBlockStates {
- BlockPos pos = craftBlock.getPosition();
- net.minecraft.world.level.block.state.BlockState state = craftBlock.getNMS();
- BlockEntity blockEntity = craftBlock.getHandle().getBlockEntity(pos);
+@@ -249,8 +249,8 @@ public final class CraftBlockStates {
+ net.minecraft.world.level.block.state.BlockState blockData = craftBlock.getNMS();
+ BlockEntity tileEntity = craftBlock.getHandle().getBlockEntity(blockPosition);
+ // Paper start - block state snapshots
- boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT;
- CraftBlockEntityState.DISABLE_SNAPSHOT = !useSnapshot;
+ boolean prev = CraftBlockEntityState.getDisableSnapshotTL(); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior)
+ CraftBlockEntityState.setDisableSnapshotTL(!useSnapshot); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior)
try {
- CraftBlockState blockState = CraftBlockStates.getBlockState(world, pos, state, blockEntity);
- blockState.setWorldHandle(craftBlock.getHandle()); // Inject the block's generator access
- return blockState;
+ // Paper end
+ CraftBlockState blockState = CraftBlockStates.getBlockState(world, blockPosition, blockData, tileEntity);
+@@ -258,7 +258,7 @@ public final class CraftBlockStates {
+ return blockState;
+ // Paper start
} finally {
- CraftBlockEntityState.DISABLE_SNAPSHOT = prev;
+ CraftBlockEntityState.setDisableSnapshotTL(prev); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior)
}
+ // Paper end
}
-
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
-index ebb07b9b9f6bef9195978c8ecdd5f4ef3ee198bc..3848011363056d700110564c6af8e4c3bd54ac6c 100644
+index c2552c3706831f7012b5b449fa43c7d5990056a4..4e8a1d01a6c0afef92ae56cc4909af06d63e5268 100644
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
-@@ -818,6 +818,28 @@ public class CraftEventFactory {
+@@ -961,6 +961,28 @@ public class CraftEventFactory {
}
- public static BlockPos sourceBlockOverride = null; // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPos up to five methods deep.
+ public static BlockPos sourceBlockOverride = null; // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep.
+ public static final ThreadLocal sourceBlockOverrideRT = new ThreadLocal<>(); // SparklyPaper - parallel world ticking (this is from Folia, fixes concurrency bugs with sculk catalysts)
+
+ // Leaf start - SparklyPaper - parallel world ticking mod
@@ -639,20 +634,29 @@ index ebb07b9b9f6bef9195978c8ecdd5f4ef3ee198bc..3848011363056d700110564c6af8e4c3
+ }
+ // Leaf end - SparklyPaper - parallel world ticking mod
- public static boolean handleBlockSpreadEvent(LevelAccessor world, BlockPos source, BlockPos target, net.minecraft.world.level.block.state.BlockState state, int flags) {
- return handleBlockSpreadEvent(world, source, target, state, flags, false);
-@@ -833,7 +855,10 @@ public class CraftEventFactory {
- CraftBlockState snapshot = CraftBlockStates.getBlockState(world, target);
- snapshot.setData(state);
+ public static boolean handleBlockSpreadEvent(LevelAccessor world, BlockPos source, BlockPos target, net.minecraft.world.level.block.state.BlockState block, int flag) {
+ // Suppress during worldgen
+@@ -972,7 +994,10 @@ public class CraftEventFactory {
+ CraftBlockState state = CraftBlockStates.getBlockState(world, target, flag);
+ state.setData(block);
-- BlockSpreadEvent event = new BlockSpreadEvent(snapshot.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverride != null ? CraftEventFactory.sourceBlockOverride : source), snapshot);
+- BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverride != null ? CraftEventFactory.sourceBlockOverride : source), state);
+ // Leaf start - SparklyPaper parallel world ticking mod (collapse original behavior)
+ final BlockPos sourceBlockOverrideRTSnap = getSourceBlockOverrideRT();
-+ BlockSpreadEvent event = new BlockSpreadEvent(snapshot.getBlock(), CraftBlock.at(world, sourceBlockOverrideRTSnap != null ? sourceBlockOverrideRTSnap : source), snapshot); // SparklyPaper - parallel world ticking
++ BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, sourceBlockOverrideRTSnap != null ? sourceBlockOverrideRTSnap : source), state); // SparklyPaper - parallel world ticking
+ // Leaf end - SparklyPaper parallel world ticking mod (collapse original behavior)
- if (event.callEvent()) {
- boolean result = snapshot.place(flags);
- return !checkSetResult || result;
+ Bukkit.getPluginManager().callEvent(event);
+
+ if (!event.isCancelled()) {
+@@ -2265,7 +2290,7 @@ public class CraftEventFactory {
+ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemStack.copyWithCount(1));
+
+ org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), CraftVector.toBukkit(to));
+- if (!net.minecraft.world.level.block.DispenserBlock.eventFired) {
++ if (!net.minecraft.world.level.block.DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior)
+ if (!event.callEvent()) {
+ return itemStack;
+ }
diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
index e4e2e42d0ca25df7fe9f2dd4275610e45fcb2c84..e7c6b2ab5f2c68f3319ccd52785c8d3488a2eef7 100644
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
diff --git a/leaf-archived-patches/unapplied/src/main/java/org/dreeam/leaf/async/world/PWTEventScheduler.java b/leaf-archived-patches/unapplied/src/main/java/org/dreeam/leaf/async/world/PWTEventScheduler.java
new file mode 100644
index 00000000..c74d8909
--- /dev/null
+++ b/leaf-archived-patches/unapplied/src/main/java/org/dreeam/leaf/async/world/PWTEventScheduler.java
@@ -0,0 +1,37 @@
+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);
+ }
+}
diff --git a/leaf-archived-patches/unapplied/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java b/leaf-archived-patches/unapplied/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java
index 428e507e..72284716 100644
--- a/leaf-archived-patches/unapplied/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java
+++ b/leaf-archived-patches/unapplied/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java
@@ -144,6 +144,10 @@ public class LinearRegionFile implements IRegionFile {
}
private synchronized void save() throws IOException {
+ if (MinecraftServer.getServer().hasStopped()) {
+ // Save only once on shutdown
+ if (!closed) return;
+ }
long timestamp = getTimestamp();
short chunkCount = 0;
@@ -217,7 +221,7 @@ public class LinearRegionFile implements IRegionFile {
LOGGER.error("Chunk write IOException {} {}", e, this.path);
}
- if ((System.nanoTime() - this.lastFlushed) >= TimeUnit.NANOSECONDS.toSeconds(RegionFormatConfig.linearFlushFrequency)) {
+ if (TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - this.lastFlushed) >= (RegionFormatConfig.linearFlushFrequency)) {
this.flushWrapper();
}
}
diff --git a/leaf-server/minecraft-patches/features/0134-Cache-player-profileResult.patch b/leaf-server/minecraft-patches/features/0134-Cache-player-profileResult.patch
index ac7be181..827e49a5 100644
--- a/leaf-server/minecraft-patches/features/0134-Cache-player-profileResult.patch
+++ b/leaf-server/minecraft-patches/features/0134-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 9dce1d22c7de3a3dd0e0e8f117cfbb54d1b15042..c4d0372feb6299b785a7e569314cc91f07870036 100644
+index 9dce1d22c7de3a3dd0e0e8f117cfbb54d1b15042..50137b67b578b0f9f34bb11a6d572df99ee9fa37 100644
--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
@@ -70,6 +70,11 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
@@ -20,7 +20,7 @@ index 9dce1d22c7de3a3dd0e0e8f117cfbb54d1b15042..c4d0372feb6299b785a7e569314cc91f
public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) {
this.server = server;
-@@ -303,9 +308,23 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
+@@ -303,9 +308,25 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
String string1 = Objects.requireNonNull(ServerLoginPacketListenerImpl.this.requestedUsername, "Player name not initialized");
try {
@@ -36,7 +36,9 @@ index 9dce1d22c7de3a3dd0e0e8f117cfbb54d1b15042..c4d0372feb6299b785a7e569314cc91f
+ 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
diff --git a/leaf-server/minecraft-patches/features/0170-Multithreaded-Tracker.patch b/leaf-server/minecraft-patches/features/0170-Multithreaded-Tracker.patch
index 5fb5a048..778b318e 100644
--- a/leaf-server/minecraft-patches/features/0170-Multithreaded-Tracker.patch
+++ b/leaf-server/minecraft-patches/features/0170-Multithreaded-Tracker.patch
@@ -177,10 +177,10 @@ index f106373ef3ac4a8685c2939c9e8361688a285913..51ae390c68e7a3aa193329cc3bc47ca6
public boolean visible = true;
diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
-index 44d87997e1ce9b846ebed541634a4478334c920c..6b421f84e2d4e286ad4b7624670c90a76e86c8fe 100644
+index 44d87997e1ce9b846ebed541634a4478334c920c..87b032ad2ba3e3e0a2e5cfcf185533102247a946 100644
--- a/net/minecraft/server/level/ServerEntity.java
+++ b/net/minecraft/server/level/ServerEntity.java
-@@ -460,12 +460,15 @@ public class ServerEntity {
+@@ -460,15 +460,18 @@ public class ServerEntity {
if (this.entity instanceof LivingEntity) {
Set attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync();
if (!attributesToSync.isEmpty()) {
@@ -197,7 +197,11 @@ index 44d87997e1ce9b846ebed541634a4478334c920c..6b421f84e2d4e286ad4b7624670c90a7
+ // Leaf end - petal - Multithreaded tracker - send in main thread
}
- attributesToSync.clear();
+- attributesToSync.clear();
++ ((LivingEntity)this.entity).getAttributes().getAttributesToSync().clear(); // Leaf - Multithreaded tracker
+ }
+ }
+
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 5943b18f172fb1d77ef1fe768daa8e8f43c3c8c1..7b85a9ebdbe3e8bee0a8fc100ede8a3f07eee5ce 100644
--- a/net/minecraft/server/level/ServerLevel.java
@@ -234,7 +238,7 @@ index c34cf83f79314198b0f7a747e4ae68b88d09d2cd..c8590517efe4124c2b1db2b927d131b8
if (this.player.isRemoved()) {
LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName());
diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
-index 3ac9f36eae87369354e992a1d9b5c5b2d87d17cb..d99bbf299af2b2d3a61761c5c3c33c4d371d1b9b 100644
+index 3ac9f36eae87369354e992a1d9b5c5b2d87d17cb..11520972f4fabde3be48edd296351113453b2869 100644
--- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
+++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
@@ -26,8 +26,11 @@ public class AttributeInstance {
@@ -245,34 +249,86 @@ index 3ac9f36eae87369354e992a1d9b5c5b2d87d17cb..d99bbf299af2b2d3a61761c5c3c33c4d
- 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;
+@@ -116,7 +119,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);
+ }
+
+@@ -143,6 +154,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 701025715e0aca3c1f920a66f9b3d03ec08eaf02..2b8b335cf5779d1b6eb639935d1b92d82aa85d7f 100644
+index 701025715e0aca3c1f920a66f9b3d03ec08eaf02..8a299c81799b3f0c353eecce56afd14b9150df5f 100644
--- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java
+++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java
-@@ -14,11 +14,14 @@ import net.minecraft.nbt.ListTag;
+@@ -14,11 +14,11 @@ import net.minecraft.nbt.ListTag;
import net.minecraft.resources.ResourceLocation;
public class AttributeMap {
-+ // 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 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 net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables
+@@ -32,6 +32,17 @@ public class AttributeMap {
+ // Purpur end - Ridables
+ this.supplier = defaultAttributes;
+ this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations
++ // Leaf start - Multithreaded tracker
++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) {
++ 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.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);
++ }
++ // Leaf end - Multithreaded tracker
+ }
+
+ private void onAttributeModified(AttributeInstance instance) {
diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
index 7bbeed6c998c91e68376d3f17a510d68e3cd0b27..d62ff9ebd4b55e1a9a0b51e84be868d844e5a954 100644
--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
diff --git a/leaf-server/minecraft-patches/features/0212-Improve-sorting-in-SortedArraySet.patch b/leaf-server/minecraft-patches/features/0211-Improve-sorting-in-SortedArraySet.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0212-Improve-sorting-in-SortedArraySet.patch
rename to leaf-server/minecraft-patches/features/0211-Improve-sorting-in-SortedArraySet.patch
diff --git a/leaf-server/minecraft-patches/features/0211-Optimize-AABB.patch b/leaf-server/minecraft-patches/features/0211-Optimize-AABB.patch
deleted file mode 100644
index c6a88a61..00000000
--- a/leaf-server/minecraft-patches/features/0211-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 939fe337c8c1fa52bc0d95cff6d6a735e1125738..1a8432a7ee220ba19327aaad007e9897628bb6cb 100644
---- a/net/minecraft/world/phys/AABB.java
-+++ b/net/minecraft/world/phys/AABB.java
-@@ -221,13 +221,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) {
-@@ -259,16 +262,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/0213-Make-removeIf-slightly-faster.patch b/leaf-server/minecraft-patches/features/0212-Make-removeIf-slightly-faster.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0213-Make-removeIf-slightly-faster.patch
rename to leaf-server/minecraft-patches/features/0212-Make-removeIf-slightly-faster.patch
diff --git a/leaf-server/minecraft-patches/features/0214-Optimize-LinearPalette.patch b/leaf-server/minecraft-patches/features/0213-Optimize-LinearPalette.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0214-Optimize-LinearPalette.patch
rename to leaf-server/minecraft-patches/features/0213-Optimize-LinearPalette.patch
diff --git a/leaf-server/minecraft-patches/features/0215-Slightly-optimized-VarInt-write.patch b/leaf-server/minecraft-patches/features/0214-Slightly-optimized-VarInt-write.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0215-Slightly-optimized-VarInt-write.patch
rename to leaf-server/minecraft-patches/features/0214-Slightly-optimized-VarInt-write.patch
diff --git a/leaf-server/minecraft-patches/features/0216-Rewrite-ClientboundLightUpdatePacketData.patch b/leaf-server/minecraft-patches/features/0215-Rewrite-ClientboundLightUpdatePacketData.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0216-Rewrite-ClientboundLightUpdatePacketData.patch
rename to leaf-server/minecraft-patches/features/0215-Rewrite-ClientboundLightUpdatePacketData.patch
diff --git a/leaf-server/minecraft-patches/features/0220-Async-chunk-sending.patch b/leaf-server/minecraft-patches/features/0216-Async-chunk-sending.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0220-Async-chunk-sending.patch
rename to leaf-server/minecraft-patches/features/0216-Async-chunk-sending.patch
diff --git a/leaf-server/minecraft-patches/features/0217-Some-Optimizations-on-SerializableChunkData.patch b/leaf-server/minecraft-patches/features/0217-Some-Optimizations-on-SerializableChunkData.patch
deleted file mode 100644
index a1bcbe2c..00000000
--- a/leaf-server/minecraft-patches/features/0217-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 749096358fccbd5d1d13801092255c51096eb001..62a40e88fc03b7f383bd750d72c42747ddd591b4 100644
---- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
-+++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
-@@ -469,14 +469,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();
-@@ -508,10 +510,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()) {
-@@ -521,7 +524,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;
-@@ -537,14 +549,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/0221-Spawner-Configurations.patch b/leaf-server/minecraft-patches/features/0217-Spawner-Configurations.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0221-Spawner-Configurations.patch
rename to leaf-server/minecraft-patches/features/0217-Spawner-Configurations.patch
diff --git a/leaf-server/minecraft-patches/features/0222-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch b/leaf-server/minecraft-patches/features/0218-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0222-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch
rename to leaf-server/minecraft-patches/features/0218-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch
diff --git a/leaf-server/minecraft-patches/features/0218-Rework-ChunkHolderManager.patch b/leaf-server/minecraft-patches/features/0218-Rework-ChunkHolderManager.patch
deleted file mode 100644
index e3b80f09..00000000
--- a/leaf-server/minecraft-patches/features/0218-Rework-ChunkHolderManager.patch
+++ /dev/null
@@ -1,133 +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 ea4010df54dbd17cdae22d671ea1e4bd7b685b3e..921ac2a1d381268060b9df07c3b2958737e3d14a 100644
---- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
-+++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
-@@ -821,24 +821,21 @@ 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
-@@ -846,11 +843,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();
-
-@@ -858,33 +859,50 @@ 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);
- }
-@@ -892,6 +910,7 @@ public final class ChunkHolderManager {
- this.ticketLockArea.unlock(ticketLock);
- }
- }
-+ // Leaf end - Rework ChunkHolderManager
-
- this.processTicketUpdates();
- }
diff --git a/leaf-server/minecraft-patches/features/0223-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch b/leaf-server/minecraft-patches/features/0219-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0223-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch
rename to leaf-server/minecraft-patches/features/0219-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch
diff --git a/leaf-server/minecraft-patches/features/0224-Remove-streams-on-InsideBrownianWalk.patch b/leaf-server/minecraft-patches/features/0220-Remove-streams-on-InsideBrownianWalk.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0224-Remove-streams-on-InsideBrownianWalk.patch
rename to leaf-server/minecraft-patches/features/0220-Remove-streams-on-InsideBrownianWalk.patch
diff --git a/leaf-server/minecraft-patches/features/0225-Use-BFS-on-getSlopeDistance.patch b/leaf-server/minecraft-patches/features/0221-Use-BFS-on-getSlopeDistance.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0225-Use-BFS-on-getSlopeDistance.patch
rename to leaf-server/minecraft-patches/features/0221-Use-BFS-on-getSlopeDistance.patch
diff --git a/leaf-server/minecraft-patches/features/0226-Paper-PR-Throttle-failed-spawn-attempts.patch b/leaf-server/minecraft-patches/features/0222-Paper-PR-Throttle-failed-spawn-attempts.patch
similarity index 98%
rename from leaf-server/minecraft-patches/features/0226-Paper-PR-Throttle-failed-spawn-attempts.patch
rename to leaf-server/minecraft-patches/features/0222-Paper-PR-Throttle-failed-spawn-attempts.patch
index 04579cf9..744ad650 100644
--- a/leaf-server/minecraft-patches/features/0226-Paper-PR-Throttle-failed-spawn-attempts.patch
+++ b/leaf-server/minecraft-patches/features/0222-Paper-PR-Throttle-failed-spawn-attempts.patch
@@ -175,7 +175,7 @@ index 6683df8d0f5a61ab094393f761a3d3a22d6e0455..4fd9313ce2c87383685d80e2533b93d5
// 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 62a40e88fc03b7f383bd750d72c42747ddd591b4..6f4f431d5197e8157908191709776ff6d406207b 100644
+index 749096358fccbd5d1d13801092255c51096eb001..ccec1b847ba1a3667206cbeac4ed541a9fb028ea 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(
@@ -230,7 +230,7 @@ index 62a40e88fc03b7f383bd750d72c42747ddd591b4..6f4f431d5197e8157908191709776ff6
if (chunkType == ChunkType.LEVELCHUNK) {
return this.loadStarlightLightData(level, new ImposterProtoChunk((LevelChunk)chunkAccess, false)); // Paper - starlight
} else {
-@@ -570,6 +594,7 @@ public record SerializableChunkData(
+@@ -554,6 +578,7 @@ public record SerializableChunkData(
persistentDataContainer = chunk.persistentDataContainer.toTagCompound();
}
// CraftBukkit end
@@ -238,7 +238,7 @@ index 62a40e88fc03b7f383bd750d72c42747ddd591b4..6f4f431d5197e8157908191709776ff6
return new SerializableChunkData(
level.registryAccess().lookupOrThrow(Registries.BIOME),
pos,
-@@ -590,6 +615,7 @@ public record SerializableChunkData(
+@@ -574,6 +599,7 @@ public record SerializableChunkData(
list1,
compoundTag
, persistentDataContainer // CraftBukkit - persistentDataContainer
@@ -246,7 +246,7 @@ index 62a40e88fc03b7f383bd750d72c42747ddd591b4..6f4f431d5197e8157908191709776ff6
);
}
}
-@@ -674,6 +700,21 @@ public record SerializableChunkData(
+@@ -658,6 +684,21 @@ public record SerializableChunkData(
compoundTag.put("ChunkBukkitValues", this.persistentDataContainer);
}
// CraftBukkit end
@@ -268,7 +268,7 @@ index 62a40e88fc03b7f383bd750d72c42747ddd591b4..6f4f431d5197e8157908191709776ff6
// 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
-@@ -881,4 +922,50 @@ public record SerializableChunkData(
+@@ -865,4 +906,50 @@ public record SerializableChunkData(
}
// Paper end - starlight - convert from record
}
diff --git a/leaf-server/minecraft-patches/features/0227-Improve-BlockEntity-ticking-isRemoved-check.patch b/leaf-server/minecraft-patches/features/0223-Improve-BlockEntity-ticking-isRemoved-check.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0227-Improve-BlockEntity-ticking-isRemoved-check.patch
rename to leaf-server/minecraft-patches/features/0223-Improve-BlockEntity-ticking-isRemoved-check.patch
diff --git a/leaf-server/minecraft-patches/features/0228-Raytrace-AntiXray-SDK-integration.patch b/leaf-server/minecraft-patches/features/0224-Raytrace-AntiXray-SDK-integration.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0228-Raytrace-AntiXray-SDK-integration.patch
rename to leaf-server/minecraft-patches/features/0224-Raytrace-AntiXray-SDK-integration.patch
diff --git a/leaf-server/minecraft-patches/features/0229-Optimize-addOrUpdateTransientModifier.patch b/leaf-server/minecraft-patches/features/0225-Optimize-addOrUpdateTransientModifier.patch
similarity index 93%
rename from leaf-server/minecraft-patches/features/0229-Optimize-addOrUpdateTransientModifier.patch
rename to leaf-server/minecraft-patches/features/0225-Optimize-addOrUpdateTransientModifier.patch
index 3962fb6a..9bea29b1 100644
--- a/leaf-server/minecraft-patches/features/0229-Optimize-addOrUpdateTransientModifier.patch
+++ b/leaf-server/minecraft-patches/features/0225-Optimize-addOrUpdateTransientModifier.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Optimize addOrUpdateTransientModifier
diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
-index d99bbf299af2b2d3a61761c5c3c33c4d371d1b9b..643a2005567e883a2ca545d0f65bc59914ddee00 100644
+index 11520972f4fabde3be48edd296351113453b2869..5fe88b105efd3546c675b3397be46bf42e830fb3 100644
--- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
+++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java
@@ -90,8 +90,13 @@ public class AttributeInstance {
diff --git a/leaf-server/minecraft-patches/features/0230-Optimize-ContextMap.create.patch b/leaf-server/minecraft-patches/features/0226-Optimize-ContextMap.create.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0230-Optimize-ContextMap.create.patch
rename to leaf-server/minecraft-patches/features/0226-Optimize-ContextMap.create.patch
diff --git a/leaf-server/minecraft-patches/features/0231-Micro-optimizations-for-random-tick.patch b/leaf-server/minecraft-patches/features/0227-Micro-optimizations-for-random-tick.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0231-Micro-optimizations-for-random-tick.patch
rename to leaf-server/minecraft-patches/features/0227-Micro-optimizations-for-random-tick.patch
diff --git a/leaf-server/minecraft-patches/features/0232-Remove-streams-on-updateConnectedPlayersWithinRange.patch b/leaf-server/minecraft-patches/features/0228-Remove-streams-on-updateConnectedPlayersWithinRange.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0232-Remove-streams-on-updateConnectedPlayersWithinRange.patch
rename to leaf-server/minecraft-patches/features/0228-Remove-streams-on-updateConnectedPlayersWithinRange.patch
diff --git a/leaf-server/minecraft-patches/features/0233-Remove-streams-on-PlayerDetector.patch b/leaf-server/minecraft-patches/features/0229-Remove-streams-on-PlayerDetector.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0233-Remove-streams-on-PlayerDetector.patch
rename to leaf-server/minecraft-patches/features/0229-Remove-streams-on-PlayerDetector.patch
diff --git a/leaf-server/minecraft-patches/features/0235-Use-direct-iteration-on-Sensing.tick.patch b/leaf-server/minecraft-patches/features/0230-Use-direct-iteration-on-Sensing.tick.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0235-Use-direct-iteration-on-Sensing.tick.patch
rename to leaf-server/minecraft-patches/features/0230-Use-direct-iteration-on-Sensing.tick.patch
diff --git a/leaf-server/minecraft-patches/features/0236-Optimise-non-flush-packet-sending.patch b/leaf-server/minecraft-patches/features/0231-Optimise-non-flush-packet-sending.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0236-Optimise-non-flush-packet-sending.patch
rename to leaf-server/minecraft-patches/features/0231-Optimise-non-flush-packet-sending.patch
diff --git a/leaf-server/minecraft-patches/features/0237-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch b/leaf-server/minecraft-patches/features/0232-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0237-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch
rename to leaf-server/minecraft-patches/features/0232-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch
diff --git a/leaf-server/minecraft-patches/features/0239-Null-handling-on-MultifaceSpreader.patch b/leaf-server/minecraft-patches/features/0233-Null-handling-on-MultifaceSpreader.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0239-Null-handling-on-MultifaceSpreader.patch
rename to leaf-server/minecraft-patches/features/0233-Null-handling-on-MultifaceSpreader.patch
diff --git a/leaf-server/minecraft-patches/features/0234-Async-Block-Finding.patch b/leaf-server/minecraft-patches/features/0234-Async-Block-Finding.patch
deleted file mode 100644
index ca8e3fac..00000000
--- a/leaf-server/minecraft-patches/features/0234-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..007da9cb39ff76285c52ce0abdff60997acdff0f 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 = pos == BlockPos.ZERO ? null : pos;
-+ 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
- return true;
- }
- }
diff --git a/leaf-server/minecraft-patches/features/0240-More-virtual-threads.patch b/leaf-server/minecraft-patches/features/0234-More-virtual-threads.patch
similarity index 100%
rename from leaf-server/minecraft-patches/features/0240-More-virtual-threads.patch
rename to leaf-server/minecraft-patches/features/0234-More-virtual-threads.patch
diff --git a/leaf-server/minecraft-patches/features/0235-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch b/leaf-server/minecraft-patches/features/0235-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch
new file mode 100644
index 00000000..5352001f
--- /dev/null
+++ b/leaf-server/minecraft-patches/features/0235-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch
@@ -0,0 +1,110 @@
+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..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 {
+
+ 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<>(); // Leaf - Replace ConcurrentLong2ReferenceChainedHashTable with custom map
+
+ /*
+ * Note: write operations do not occur in parallel for any given section.
+@@ -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);
+diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
+index ea4010df54dbd17cdae22d671ea1e4bd7b685b3e..9f771d78f5401ce8776de38d11e453e8e5857572 100644
+--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
+@@ -72,11 +72,13 @@ public final class ChunkHolderManager {
+ private static final long NO_TIMEOUT_MARKER = Long.MIN_VALUE;
+ 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); // Leaf - Replace ConcurrentLong2ReferenceChainedHashTable with custom map
+ private final ServerLevel world;
+ private final ChunkTaskScheduler taskScheduler;
+ private long currentTick;
+@@ -1502,9 +1504,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(); // 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(); // 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..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 {
+ }
+
+ private final UpdateQueue updateQueue;
+- private final ConcurrentLong2ReferenceChainedHashTable 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<>(); // 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 e1812910d7c3941dec3d4f1c90f4cf966a631de3..87c229db79f6c6dc98811c7cbccbe5c549fd744f 100644
+--- a/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java
++++ b/ca/spottedleaf/moonrise/patches/starlight/light/StarLightInterface.java
+@@ -739,7 +739,7 @@ public final class StarLightInterface {
+
+ 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<>(); // 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 0f9d18dd29e210ad656da211a3cb1cb25cd4efb1..f89fb321e50338e7765476cb5d7bdf2f02a497b3 100644
+--- a/net/minecraft/server/level/ServerChunkCache.java
++++ b/net/minecraft/server/level/ServerChunkCache.java
+@@ -76,7 +76,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<>(); // Leaf - Replace ConcurrentLong2ReferenceChainedHashTable with custom map
+ public int getFullChunksCount() {
+ return this.fullChunks.size();
+ }
diff --git a/leaf-server/minecraft-patches/features/0236-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0236-Async-target-finding.patch
new file mode 100644
index 00000000..8c3f3abd
--- /dev/null
+++ b/leaf-server/minecraft-patches/features/0236-Async-target-finding.patch
@@ -0,0 +1,2001 @@
+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 508f3c7c1e806576f1faea7975ac9bbe73b1024b..78aee57ad8224b0728411c699d2e3844847c9c79 100644
+--- a/net/minecraft/server/MinecraftServer.java
++++ b/net/minecraft/server/MinecraftServer.java
+@@ -290,6 +290,7 @@ 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)
++ @Nullable public org.dreeam.leaf.async.ai.AsyncGoalThread asyncGoalThread; // Leaf - Async target finding
+
+ public static S spin(Function threadFunction) {
+ AtomicReference atomicReference = new AtomicReference<>();
+@@ -1073,6 +1074,14 @@ 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
+@@ -212,6 +221,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent
+ private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current)
+ public boolean hasRidableMoveEvent = false; // Purpur - Ridables
++ public final @Nullable org.dreeam.leaf.async.ai.AsyncGoalExecutor asyncGoalExecutor; // Leaf - Async target finding
+
+ @Override
+ public @Nullable LevelChunk getChunkIfLoaded(int x, int z) {
+@@ -330,6 +340,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);
+@@ -691,6 +707,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ // Paper end - rewrite chunk system
+ this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
+ this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle
++ // 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
+@@ -835,12 +858,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();
++ if (this.asyncGoalExecutor != null) this.asyncGoalExecutor.unpark(); // Leaf - Async target finding
+ }
+ // Paper - rewrite chunk system
+ }
+@@ -1312,6 +1337,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ }
+ // Paper end - rewrite chunk system
+
++ this.leafMidTickTasks(); // Leaf - Async target finding
+ }
+
+ private void tickBlock(BlockPos pos, Block block) {
+@@ -1325,6 +1351,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ }
+ // 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 658b8af9b1db252d5664956907a5c0db538018b0..960ae487a5a88a5fe9899c16b9553cea0fbfba37 100644
+--- a/net/minecraft/world/entity/Entity.java
++++ b/net/minecraft/world/entity/Entity.java
+@@ -251,6 +251,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 - 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;
+@@ -4988,7 +4989,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+
+ @Override
+ public final boolean isRemoved() {
+- return this.removalReason != null;
++ // 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 - Async target finding
+ }
+
+ @Nullable
+@@ -5008,6 +5016,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 - Async target finding
++ // Volatile removal check
++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled && this.removalReason != null) {
++ this.isRemoved = true;
++ }
++ // Leaf end - Async target finding
+ }
+
+ if (this.removalReason.shouldDestroy()) {
+@@ -5027,6 +5041,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+
+ public void unsetRemoved() {
+ this.removalReason = null;
++ // Leaf start - Async target finding
++ // Volatile removal check
++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) {
++ this.isRemoved = false;
++ }
++ // 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 77075bf43e7e2301e58aa6d9f8bfe934cce42f55..4a21420a3c7112024aba1a91bf635872afecf51a 100644
+--- a/net/minecraft/world/entity/Mob.java
++++ b/net/minecraft/world/entity/Mob.java
+@@ -135,6 +135,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
++ // 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 extends Mob> entityType, Level level) {
+ super(entityType, level);
+@@ -213,12 +219,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 - 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 - Async target finding
+ 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 end - Async target finding
+ }
+ // Paper end
+
+@@ -765,17 +780,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 7651676e72fcec52d7c1f9f7d7b6f9e585015c4d..28aae1f1115c3a5dc5b3b585621ce32e4e759635 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() {
++ // Leaf start - Async Avoid Entity Finding
++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) {
++ if (!poll()) {
++ getNearestEntityAsync();
++ 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.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,45 @@ public class AvoidEntityGoal extends Goal {
+ }
+ }
+
++ // Leaf start - Async Avoid Entity Finding
++ private boolean poll() {
++ if (!(this.mob.getGoalCtx().result() instanceof LivingEntity target)) return false;
++ var serverLevel = getServerLevel(this.mob);
++ if (serverLevel == null || !target.isAlive() || !this.avoidEntityTargeting.test(serverLevel, this.mob, target)) return false;
++ this.toAvoid = (T) target;
++ return true;
++ }
++
++ private void getNearestEntityAsync() {
++ final var mob = this.mob;
++ 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);
++ ctx.wake = () -> {
++ try {
++ if (mob == null || mob.isRemoved() || !mob.isAlive() || mob.level() != serverLevel) {
++ return;
++ }
++ ctx.result = serverLevel.getNearestEntity(
++ serverLevel.getEntitiesOfClass(avoidClass, bound, livingEntity -> true),
++ avoidEntityTargeting,
++ mob,
++ x,
++ y,
++ z
++ );
++ } catch (Exception e) {
++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e);
++ }
++ };
++ }
++ // 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 6f7767d1ef5ee20338a334d85ad58dab9f8c4d1b..d5a8d0fc6d28e9cd770b76a569fd73d2601c8784 100644
+--- a/net/minecraft/world/entity/ai/goal/BegGoal.java
++++ b/net/minecraft/world/entity/ai/goal/BegGoal.java
+@@ -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 target)) return false;
++ if (target == null) return false;
++ ServerLevel serverLevel = getServerLevel(this.wolf);
++ if (serverLevel == null || !target.isAlive() || !playerHoldingInteresting(target)) return false;
++ this.player = target;
++ return true;
++ }
++
++ private void findTargetAsync() {
++ final Wolf wolf = this.wolf;
++ final var ctx = wolf.getGoalCtx();
++ if (!ctx.state) return;
++ final ServerLevel serverLevel = getServerLevel(wolf);
++ final TargetingConditions begTargeting = this.begTargeting;
++ ctx.wake = () -> {
++ try {
++ if (wolf.level() != serverLevel || wolf.isRemoved() || !wolf.isAlive()) {
++ return;
++ }
++
++ var player = serverLevel.getNearestPlayer(begTargeting, wolf);
++ if (player != null && playerHoldingInteresting(player)) {
++ ctx.result = player;
++ }
++ } catch (Exception e) {
++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e);
++ }
++ };
++ }
++ // 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) {
++ 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,10 +101,10 @@ public class BegGoal extends Goal {
+ this.lookTime--;
+ }
+
+- private 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)) { // Leaf end - Async Target Finding
+ return true;
+ }
+ }
+diff --git a/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java b/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java
+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,13 @@ public class CatLieOnBedGoal extends MoveToBlockGoal {
+
+ @Override
+ protected boolean isValidTarget(LevelReader level, BlockPos pos) {
+- 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
++ @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..057090e3134048e75dbaefb703e8f2d35a172a3b 100644
+--- a/net/minecraft/world/entity/ai/goal/CatSitOnBlockGoal.java
++++ b/net/minecraft/world/entity/ai/goal/CatSitOnBlockGoal.java
+@@ -44,14 +44,21 @@ public class CatSitOnBlockGoal extends MoveToBlockGoal {
+
+ @Override
+ protected boolean isValidTarget(LevelReader level, BlockPos pos) {
+- if (!level.isEmptyBlock(pos.above())) {
++ if (!level.isEmptyBlock(pos.above())) { // Leaf - Async search block - diff on change
+ return false;
+ } else {
+- 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
+ : blockState.is(Blocks.FURNACE) && blockState.getValue(FurnaceBlock.LIT)
+ || 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 f7dcd341444059f8fb9708e9a21b5d8ace6b9cf9..11c428833d43f867f9598093b13fe8bd6657e4ba 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 {
+ this.mob = mob;
+ }
+
++ // Leaf start - Async Target Finding
++ private boolean poll() {
++ if (!(this.mob.getGoalCtx().result() instanceof Player target)) return false;
++ var serverLevel = getServerLevel(this.mob);
++ return serverLevel != null && target.isAlive() && !target.isSpectator() && (Mth.abs(target.xxa) > 0.0F || Mth.abs(target.zza) > 0.0F);
++ }
++
++ private void findTargetAsync() {
++ final PathfinderMob mob = this.mob;
++ final var ctx = mob.getGoalCtx();
++ if (!ctx.state) return;
++ final var bound = mob.getBoundingBox().inflate(5.0);
++ final var serverLevel = getServerLevel(mob);
++ ctx.wake = () -> {
++ 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)) {
++ ctx.result = player;
++ break;
++ }
++ }
++ } catch (Exception e) {
++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e);
++ }
++ };
++ }
++ // 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;
+
+@@ -36,7 +82,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 - 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..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 {
+
+ @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 +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 target)) return false;
++ var serverLevel = getServerLevel(this.mob);
++ if (serverLevel == null || !target.isAlive() || target.isInvisible()) return false;
++ this.followingMob = target;
++ return true;
++ }
++
++ private void getFollowingMobAsync() {
++ final var mob = this.mob;
++ 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;
++ ctx.wake = () -> {
++ 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()) {
++ ctx.result = follow;
++ return;
++ }
++ }
++ }
++ } catch (Exception e) {
++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e);
++ }
++ };
++ }
++ // 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..80f9de7b2c03c1477dae0f42b328e0100f78e58b 100644
+--- a/net/minecraft/world/entity/ai/goal/FollowParentGoal.java
++++ b/net/minecraft/world/entity/ai/goal/FollowParentGoal.java
+@@ -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 target)) return false;
++ var serverLevel = getServerLevel(animal);
++ if (serverLevel == null || !target.isAlive() || animal.distanceToSqr(target) < 9.0) return false;
++ this.parent = animal;
++ return true;
++ }
++
++ private void findTargetAsync() {
++ final Animal animal = this.animal;
++ 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();
++ ctx.wake = () -> {
++ try {
++ if (animal.level() != serverLevel || animal.isRemoved() || !animal.isAlive()) {
++ return;
++ }
++
++ List extends Animal> 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) {
++ ctx.result = target;
++ }
++ } catch (Exception e) {
++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e);
++ }
++ };
++ }
++ // 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 extends Animal> entitiesOfClass = this.animal
+ .level()
+ .getEntitiesOfClass((Class extends Animal>)this.animal.getClass(), this.animal.getBoundingBox().inflate(8.0, 4.0, 8.0));
+@@ -43,6 +98,7 @@ public class FollowParentGoal extends Goal {
+ if (animal == null) {
+ return false;
+ } else if (d < 9.0) {
++ // 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..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 {
+ 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,111 @@ public class GoalSelector {
+ return true;
+ }
+
++ // Leaf start - Async target finding
++ 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;
++ }
++ // 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();
+diff --git a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java
+index be59d0c27a83b329ec3f97c029cfb9c114e22472..7c54964a5329e12a9a4a1fdb2417ccbd3f0bfae2 100644
+--- a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java
++++ b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java
+@@ -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 target)) return null;
++ var serverLevel = getServerLevel(this.llama);
++ if (serverLevel == null || !target.isAlive()) return null;
++ return target;
++ }
++
++ private void findTargetAsync() {
++ final Llama llama = this.llama;
++ 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();
++ ctx.wake = () -> {
++ 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) {
++ ctx.result = target;
++ }
++ } catch (Exception e) {
++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e);
++ }
++ };
++ }
++ // 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 - Async Target Finding
++ // double d = Double.MAX_VALUE; // Leaf - Async Target Finding
+
+ for (Entity entity : entities) {
+ Llama llama1 = (Llama)entity;
+- 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)) {
+ d = d1;
+@@ -45,7 +118,7 @@ public class LlamaFollowCaravanGoal extends Goal {
+ if (llama == null) {
+ for (Entity entityx : entities) {
+ Llama llama1 = (Llama)entityx;
+- 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)) {
+ d = d1;
+@@ -54,6 +127,7 @@ public class LlamaFollowCaravanGoal extends Goal {
+ }
+ }
+ }
++ } // 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..0afad8ddb0e9d083d47b96627d999f0ee28b9446 100644
+--- a/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java
++++ b/net/minecraft/world/entity/ai/goal/LookAtPlayerGoal.java
+@@ -48,32 +48,87 @@ 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;
++ }
++ 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) {
+- 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;
++ }
+
+- return this.lookAt != null;
++ protected boolean poll() {
++ 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 (!target.isAlive() || !this.lookAtContext.test(serverLevel, this.mob, target)) return false;
++ this.lookAt = target;
++ return true;
++ }
++
++ protected void getLookAsync() {
++ final var mob = this.mob;
++ 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 lookAtContext = this.lookAtContext;
++ final var lookAtType = this.lookAtType;
++ ctx.wake = () -> {
++ try {
++ if (mob == null || !mob.isAlive() || mob.level() != serverLevel) {
++ return;
++ }
++
++ if (lookAtType == Player.class) {
++ ctx.result = serverLevel.getNearestPlayer(lookAtContext, mob, x, y, z);
++ } else {
++ ctx.result = serverLevel.getNearestEntity(
++ serverLevel
++ .getEntitiesOfClass(lookAtType, bound, livingEntity -> true),
++ lookAtContext,
++ mob,
++ x,
++ y,
++ z
++ );
++ }
++ } catch (Exception e) {
++ 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..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 {
+ this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.JUMP));
+ }
+
++ // Leaf start - Async search block
++ protected boolean poll() {
++ 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)) {
++ return false;
++ }
++ this.blockPos = blockPos1;
++ this.mob.movingTarget = blockPos1 == BlockPos.ZERO ? null : blockPos1;
++ return true;
++ }
++
++ protected void getBlockAsync() {
++ final var mob = this.mob;
++ final var ctx = mob.getGoalCtx();
++ if (!ctx.state) return;
++ final var serverLevel = getServerLevel(mob);
++ 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();
++ ctx.wake = () -> {
++ try {
++ if (mob == null || !mob.isAlive() || mob.level() != serverLevel) {
++ return;
++ }
++ findNearestBlockAsync(ctx, ty, toRemove, mob, serverLevel, verticalSearchStart, searchRange, verticalSearchRange, blockPos, restrictRadius, restrictCenter);
++ } catch (Exception e) {
++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting block", e);
++ }
++ };
++ }
++
++ 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 +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 +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,
++ @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)) {
++ ctx.result = 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;
++ }
++ // 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 type,
++ @org.jetbrains.annotations.Nullable net.minecraft.world.level.block.Block blockToRemoveTy,
++ PathfinderMob mob,
++ LevelReader level,
++ BlockPos pos
++ ) {
++ switch (type) {
++ 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();
++ }
++ // 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 4ba5f7da27f7f6842790c0093bc0f25e138fa982..1dcae83aa6b9e9318d320a17bf17a0b0eae80a60 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().isBrightOutside()) {
+ 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,45 @@ public class OfferFlowerGoal extends Goal {
+ }
+ }
+
++
++ // Leaf start - Async look finding
++ protected boolean poll() {
++ if (!(this.golem.getGoalCtx().result() instanceof Villager target)) return false;
++ var serverLevel = getServerLevel(this.golem);
++ if (!target.isAlive() || !OFFER_TARGER_CONTEXT.test(serverLevel, this.golem, target)) return false;
++ this.villager = target;
++ return true;
++ }
++
++ protected void getVillagerAsync() {
++ final var golem = this.golem;
++ 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);
++ ctx.wake = () -> {
++ try {
++ if (golem == null || !golem.isAlive() || golem.level() != serverLevel) {
++ return;
++ }
++ ctx.result = serverLevel.getNearestEntity(
++ serverLevel.getEntitiesOfClass(Villager.class, bound, livingEntity -> true),
++ OFFER_TARGER_CONTEXT,
++ golem,
++ x,
++ y,
++ z
++ );
++ } catch (Exception e) {
++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e);
++ }
++ };
++ }
++ // 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 084156166905562beb8d36db400bc9cab5c14be2..3eed1ac1cab130034f47da6efa33c0ad2a77a7e2 100644
+--- a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
++++ b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
+@@ -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()) {
+@@ -151,8 +158,15 @@ public class RemoveBlockGoal extends MoveToBlockGoal {
+ protected boolean isValidTarget(LevelReader level, BlockPos pos) {
+ 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) // Leaf - Async search block - diff on change
+ && 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..e42f77fc6d04a900fd8adb4682678f9218561aa8 100644
+--- a/net/minecraft/world/entity/ai/goal/TemptGoal.java
++++ b/net/minecraft/world/entity/ai/goal/TemptGoal.java
+@@ -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 target)) return false;
++ var serverLevel = getServerLevel(this.mob);
++ if (!target.isAlive() || !this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)).test(serverLevel, this.mob, target)) return false;
++ this.player = target;
++ return true;
++ }
++
++ private void getNearestPlayerAsync() {
++ final var mob = this.mob;
++ final var ctx = mob.getGoalCtx();
++ if (!ctx.state) return;
++ final var serverLevel = getServerLevel(mob);
++ final var conditions = this.targetingConditions
++ .range(mob.getAttributeValue(Attributes.TEMPT_RANGE))
++ .copy();
++ ctx.wake = () -> {
++ try {
++ if (mob == null || mob.isRemoved() || !mob.isAlive() || mob.level() != serverLevel) {
++ return;
++ }
++ ctx.result = serverLevel.getNearestPlayer(conditions, mob);
++ } catch (Exception e) {
++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting player", e);
++ }
++ };
++ }
++ // Leaf end - Async Tempt Finding
+ @Override
+ public boolean canUse() {
+ if (this.calmDown > 0) {
+ this.calmDown--;
+ return false;
+ } else {
++ // Leaf start - Async Tempt Finding
++ 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);
++ if (event.isCancelled()) {
++ return false;
++ }
++ this.player = (event.getTarget() == null) ? null : ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle();
++ }
++ if (this.player != null) {
++ return true;
++ }
++ } else {
++ getNearestPlayerAsync();
++ return false;
++ }
++ }
++ // Leaf end - Async Tempt Finding
+ this.player = getServerLevel(this.mob)
+ .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 fb160a59c873d5c3f2c3d31966ca1a653f1b384d..47802c05cec6a0d8b64a592332d71259d91d5d85 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 - Async Target Finding - static
+
+ public DefendVillageTargetGoal(IronGolem golem) {
+ super(golem, false, true);
+@@ -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 target)) return false;
++ ServerLevel serverLevel = getServerLevel(this.mob);
++ 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;
++ }
++
++ private void findTargetAsync() {
++ final IronGolem mob = this.golem;
++ 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);
++ ctx.wake = () -> {
++ 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) {
++ ctx.result = player;
++ break;
++ }
++ }
++ }
++ } catch (Exception e) {
++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e);
++ }
++ };
++ }
++ // 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 extends LivingEntity> nearbyEntities = serverLevel.getNearbyEntities(Villager.class, this.attackTargeting, this.golem, aabb);
+@@ -42,7 +92,7 @@ public class DefendVillageTargetGoal extends TargetGoal {
+ }
+ }
+
+- return this.potentialTarget != null && !(this.potentialTarget instanceof Player player1 && (player1.isSpectator() || player1.isCreative()));
++ return this.potentialTarget != null && !(this.potentialTarget instanceof Player player1 && (player1.isSpectator() || player1.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 a8ec1d5f4b0fb0ff26a234235b7d8d9c4b4a2a98..24317ff50332e624a5587a0273082005ab3f36ed 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 {
+ 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 ctx = self.getGoalCtx();
++ if (!ctx.state) return;
++ final var serverLevel = getServerLevel(self);
++ final var toIgnoreAlert = this.toIgnoreAlert;
++ ctx.wake = () -> {
++ try {
++ if (self == null || self.level() != serverLevel) {
++ return;
++ }
++ var toAlert = new java.util.ArrayList();
++ List extends Mob> 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);
++ }
++ }
++ ctx.result = toAlert;
++ } catch (Exception e) {
++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception alertOthers", e);
++ }
++ };
++ return;
++ }
++ // Leaf end - Async alert other
++
+ List extends Mob> entitiesOfClass = this.mob
+ .level()
+ .getEntitiesOfClass((Class extends Mob>)this.mob.getClass(), aabb, EntitySelector.NO_SPECTATORS);
+@@ -87,7 +137,7 @@ public class HurtByTargetGoal extends TargetGoal {
+
+ mob = (Mob)var5.next();
+ if (this.mob != mob
+- && mob.getTarget() == null
++ && mob.getTarget() == null // Leaf - Async alert other - diff on change
+ && (!(this.mob instanceof TamableAnimal) || ((TamableAnimal)this.mob).getOwner() == ((TamableAnimal)mob).getOwner())
+ && !mob.isAlliedTo(this.mob.getLastHurtByMob())) {
+ if (this.toIgnoreAlert == null) {
+@@ -96,7 +146,7 @@ public class HurtByTargetGoal extends TargetGoal {
+
+ boolean flag = false;
+
+- for (Class> clazz : this.toIgnoreAlert) {
++ for (Class> clazz : this.toIgnoreAlert) { // Leaf - Async alert other - diff on change
+ if (mob.getClass() == clazz) {
+ flag = true;
+ break;
+@@ -113,6 +163,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); // 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 1cd8143c938237ce035fa866d4f2ed1e2cd1ebca..719dbfb7c9f463767bbee33ecb9d764be06622a3 100644
+--- a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java
++++ b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java
+@@ -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 target)) return false;
++ ServerLevel serverLevel = getServerLevel(this.mob);
++ if (serverLevel == null || !target.isAlive() || !this.getTargetConditions().test(serverLevel, this.mob, target)) return false;
++ this.target = target;
++ return true;
++ }
++
++ private void findTargetAsync() {
++ final Mob mob = this.mob;
++ 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 TargetingConditions targetConditions = this.getTargetConditions().copy();
++ final Class targetType = this.targetType;
++ final AABB targetSearch = getTargetSearchArea(this.getFollowDistance());
++ final ServerLevel serverLevel = getServerLevel(mob);
++ ctx.wake = () -> {
++ try {
++ if (mob.level() != serverLevel || mob.isRemoved() || !mob.isAlive()) {
++ return;
++ }
++
++ if (targetType != Player.class && targetType != ServerPlayer.class) {
++ ctx.result = serverLevel.getNearestEntity(mob.level().getEntitiesOfClass(targetType, targetSearch, entity -> entity != null && entity != mob && entity.isAlive()), targetConditions, mob, x,y,z);
++ } else {
++ ctx.result = serverLevel.getNearestPlayer(targetConditions, mob, x, y, z);
++ }
++ } catch (Exception e) {
++ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e);
++ }
++ };
++ }
++ // 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 +100,15 @@ public class NearestAttackableTargetGoal extends TargetG
+
+ protected void findTarget() {
+ ServerLevel serverLevel = getServerLevel(this.mob);
++
++ // Leaf start - Async Target Finding
++ 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),
+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..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
+
+ @Override
+ public boolean canContinueToUse() {
+- return this.targetConditions != null ? this.targetConditions.test(getServerLevel(this.mob), this.mob, this.target) : super.canContinueToUse();
++ // Leaf start - Async target finding
++ 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 - 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..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
++ 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);
++ ctx.wake = () -> {
++ 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);
++ }
++ }
++ 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)
+@@ -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 extends Mob> getNearbyMobsOfSameType() {
++ // 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 extends Mob>)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..6fa0b8defbd1d06b3bf5d9b32ffd08f32c1fb707 100644
+--- a/net/minecraft/world/entity/ai/sensing/Sensing.java
++++ b/net/minecraft/world/entity/ai/sensing/Sensing.java
+@@ -33,6 +33,17 @@ public class Sensing {
+ }
+
+ public void tick() {
++ // Leaf start - Async target finding
++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) {
++ synchronized (this) {
++ tickInternal();
++ }
++ } else {
++ tickInternal();
++ }
++ }
++ private void tickInternal() {
++ // Leaf end - Async target finding
+ if (this.expiring == null) { // Gale - Petal - reduce line of sight updates
+ this.seen.clear();
+ // Gale start - Petal - reduce line of sight updates
+@@ -63,6 +74,17 @@ public class Sensing {
+ }
+
+ public boolean hasLineOfSight(Entity entity) {
++ // Leaf start - Async target finding
++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) {
++ synchronized (this) {
++ return hasLineOfSightInternal(entity);
++ }
++ } else {
++ return hasLineOfSightInternal(entity);
++ }
++ }
++ 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 fcda990a5575288b39f6aa16b0ec48d2a57542e9..fb956c0bd8a877117ec33f753f6e6bb096831f78 100644
+--- a/net/minecraft/world/entity/animal/Fox.java
++++ b/net/minecraft/world/entity/animal/Fox.java
+@@ -870,6 +870,11 @@ public class Fox extends Animal {
+ return false;
+ } else {
+ ServerLevel serverLevel = getServerLevel(Fox.this.level());
++ // Leaf start - Async target finding
++ if (serverLevel == null) {
++ return false;
++ }
++ // Leaf end - Async target finding
+
+ for (EntityReference entityReference : Fox.this.getTrustedEntities().toList()) {
+ LivingEntity livingEntity = entityReference.getEntity(serverLevel, LivingEntity.class);
+@@ -877,7 +882,7 @@ public class Fox extends Animal {
+ 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 - Async target finding
+ }
+ }
+
+@@ -1056,7 +1061,7 @@ public class Fox extends Animal {
+ @Override
+ protected boolean isValidTarget(LevelReader level, BlockPos pos) {
+ 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); // Leaf - Async search block - diff on change
+ }
+
+ @Override
+@@ -1120,6 +1125,13 @@ public class Fox extends Animal {
+ 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 99d99d59ec0eb13dc40bc88bd70ad884bb9e2859..d0be031cedcc895b8cca8666f826628ac8ce6ab3 100644
+--- a/net/minecraft/world/entity/animal/Panda.java
++++ b/net/minecraft/world/entity/animal/Panda.java
+@@ -988,9 +988,18 @@ 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 (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 38e2698c5084f0c14c7fde5de542ed61a7cb7f1d..d96eb5a6fde72a9e7e7799624b63ec72e9b77f90 100644
+--- a/net/minecraft/world/entity/animal/Rabbit.java
++++ b/net/minecraft/world/entity/animal/Rabbit.java
+@@ -669,7 +669,13 @@ public class Rabbit extends Animal {
+ this.wantsToRaid = this.rabbit.wantsMoreFood();
+ }
+
+- return super.canUse();
++ // Leaf start - Async search block
++ if (this.wantsToRaid && !this.canRaid) {
++ return super.canUse();
++ } else {
++ return false;
++ }
++ // Leaf end - Async search block
+ }
+
+ @Override
+@@ -712,7 +718,7 @@ public class Rabbit extends Animal {
+ @Override
+ protected boolean isValidTarget(LevelReader level, BlockPos pos) {
+ BlockState blockState = level.getBlockState(pos);
+- 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());
+ if (blockState.getBlock() instanceof CarrotBlock && ((CarrotBlock)blockState.getBlock()).isMaxAge(blockState)) {
+ this.canRaid = true;
+@@ -722,6 +728,13 @@ public class Rabbit extends Animal {
+
+ 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 c84f63f064a7769761f75cdedaceacde858b9b4e..9b7d059bf68ff24dddedb16ec62a9b67e3ceace9 100644
+--- a/net/minecraft/world/entity/animal/Turtle.java
++++ b/net/minecraft/world/entity/animal/Turtle.java
+@@ -482,8 +482,15 @@ public class Turtle extends Animal {
+
+ @Override
+ protected boolean isValidTarget(LevelReader level, BlockPos pos) {
+- 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
++ @Override
++ protected TypeToCheck typeToCheck() {
++ return TypeToCheck.TurtleToWater;
++ }
++ // Leaf end - Async search block
+ }
+
+ static class TurtleLayEggGoal extends MoveToBlockGoal {
+@@ -537,8 +544,15 @@ public class Turtle extends Animal {
+
+ @Override
+ protected boolean isValidTarget(LevelReader level, BlockPos 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/monster/Drowned.java b/net/minecraft/world/entity/monster/Drowned.java
+index 2e04078664cd723e3e0c80565e4b6e6416b13901..ed790a407b4be9e2f7fcb8cf36b8d03c32864a59 100644
+--- a/net/minecraft/world/entity/monster/Drowned.java
++++ b/net/minecraft/world/entity/monster/Drowned.java
+@@ -397,7 +397,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
+ @Override
+ protected boolean isValidTarget(LevelReader level, BlockPos pos) {
+ BlockPos blockPos = pos.above();
+- return level.isEmptyBlock(blockPos) && level.isEmptyBlock(blockPos.above()) && level.getBlockState(pos).entityCanStandOn(level, pos, this.drowned);
++ return level.isEmptyBlock(blockPos) && level.isEmptyBlock(blockPos.above()) && level.getBlockState(pos).entityCanStandOn(level, pos, this.drowned); // Leaf - Async search block - diff on change
+ }
+
+ @Override
+@@ -411,6 +411,13 @@ public class Drowned extends Zombie implements RangedAttackMob {
+ public void stop() {
+ super.stop();
+ }
++
++ // Leaf start - Async search block
++ @Override
++ protected TypeToCheck typeToCheck() {
++ return TypeToCheck.Drowned;
++ }
++ // Leaf end - Async search block
+ }
+
+ static class DrownedGoToWaterGoal extends Goal {
+diff --git a/net/minecraft/world/entity/monster/Strider.java b/net/minecraft/world/entity/monster/Strider.java
+index e1717b5c854aa81fdd7b7e715d7c3498d9f86072..3627e7479b4deea28e268245410ec4cd48f24e9e 100644
+--- a/net/minecraft/world/entity/monster/Strider.java
++++ b/net/minecraft/world/entity/monster/Strider.java
+@@ -551,8 +551,15 @@ public class Strider extends Animal implements ItemSteerable {
+
+ @Override
+ protected boolean isValidTarget(LevelReader level, BlockPos pos) {
+- return level.getBlockState(pos).is(Blocks.LAVA) && level.getBlockState(pos.above()).isPathfindable(PathComputationType.LAND);
++ return level.getBlockState(pos).is(Blocks.LAVA) && level.getBlockState(pos.above()).isPathfindable(PathComputationType.LAND); // Leaf - Async search block - diff on change
+ }
++
++ // Leaf start - Async search block
++ @Override
++ protected TypeToCheck typeToCheck() {
++ return TypeToCheck.Strider;
++ }
++ // Leaf end - Async search block
+ }
+
+ static class StriderPathNavigation extends GroundPathNavigation {
+diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
+index 20be5c4200bfee01d62fa1103a3663b91107d5d8..5fbe3356ba9b7c4c09f87fda59971552ef6bb676 100644
+--- a/net/minecraft/world/level/Level.java
++++ b/net/minecraft/world/level/Level.java
+@@ -1525,6 +1525,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl
+ ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks();
+ }
+ // Paper end - rewrite chunk system
++ ((ServerLevel) this).leafMidTickTasks(); // Leaf - Async target finding
+ }
+ }
+ this.blockEntityTickers.removeMarkedEntries(); // SparklyPaper - optimize block entity removals
+@@ -1788,7 +1789,11 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl
+
+ @Override
+ public List getEntities(@Nullable Entity entity, AABB boundingBox, Predicate super Entity> predicate) {
+- 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)
++ // 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/0237-Optimize-ThreadedTicketLevelPropagator.patch b/leaf-server/minecraft-patches/features/0237-Optimize-ThreadedTicketLevelPropagator.patch
new file mode 100644
index 00000000..9c5503b2
--- /dev/null
+++ b/leaf-server/minecraft-patches/features/0237-Optimize-ThreadedTicketLevelPropagator.patch
@@ -0,0 +1,74 @@
+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 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; // Leaf - Optimize ThreadedTicketLevelPropagator
+
+ final Long2ByteLinkedOpenHashMap updatedPositions = this.updatedPositions;
+
+@@ -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];
++ 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);
++ // Leaf end - Optimize ThreadedTicketLevelPropagator
+ }
+
+ // this bitset represents the values that we have not propagated to
+@@ -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]; // 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 +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; // Leaf - Optimize ThreadedTicketLevelPropagator
+
+ final Long2ByteLinkedOpenHashMap updatedPositions = this.updatedPositions;
+
+@@ -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]; // 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/0238-Async-Target-Finding.patch b/leaf-server/minecraft-patches/features/0238-Async-Target-Finding.patch
deleted file mode 100644
index 0eab7d26..00000000
--- a/leaf-server/minecraft-patches/features/0238-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 024792900c8ab716e91ef512d2da22548075044d..7f256793232cfa9666728223cb9964e49ff8b6ba 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 extends LivingEntity> 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 extends Player> 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); // 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);
-+ } 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/0238-Optimise-MobEffectUtil-getDigSpeedAmplification.patch b/leaf-server/minecraft-patches/features/0238-Optimise-MobEffectUtil-getDigSpeedAmplification.patch
new file mode 100644
index 00000000..9a1be992
--- /dev/null
+++ b/leaf-server/minecraft-patches/features/0238-Optimise-MobEffectUtil-getDigSpeedAmplification.patch
@@ -0,0 +1,39 @@
+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 93a07a96f74e3ba73986324b39923c6a2802f8ee..e900c91c8eb36029726f7833df1d9be4030b3ad8 100644
+--- a/net/minecraft/world/effect/MobEffectUtil.java
++++ b/net/minecraft/world/effect/MobEffectUtil.java
+@@ -27,17 +27,21 @@ public final class MobEffectUtil {
+ }
+
+ public static int getDigSpeedAmplification(LivingEntity entity) {
+- int i = 0;
+- int i1 = 0;
+- if (entity.hasEffect(MobEffects.HASTE)) {
+- i = entity.getEffect(MobEffects.HASTE).getAmplifier();
++ // Leaf start - Optimise MobEffectUtil#getDigSpeedAmplification
++ int digAmplifier = 0;
++ int conduitAmplifier = 0;
++ MobEffectInstance digEffect = entity.getEffect(MobEffects.HASTE);
++ if (digEffect != null) {
++ 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) {
++ conduitAmplifier = conduitEffect.getAmplifier();
+ }
+
+- 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/0219-Optimize-chunkUnload.patch b/leaf-server/minecraft-patches/features/0239-Optimise-chunkUnloads.patch
similarity index 56%
rename from leaf-server/minecraft-patches/features/0219-Optimize-chunkUnload.patch
rename to leaf-server/minecraft-patches/features/0239-Optimise-chunkUnloads.patch
index c56a2883..891f438b 100644
--- a/leaf-server/minecraft-patches/features/0219-Optimize-chunkUnload.patch
+++ b/leaf-server/minecraft-patches/features/0239-Optimise-chunkUnloads.patch
@@ -1,7 +1,7 @@
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/starlight/light/SWMRNibbleArray.java b/ca/spottedleaf/moonrise/patches/starlight/light/SWMRNibbleArray.java
@@ -211,3 +211,131 @@ index 36c033b0ee63dfc273d721fb4b614733e8fdef19..dc9f1bc6dd8b1057da3416e24f15f232
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 ccec1b847ba1a3667206cbeac4ed541a9fb028ea..5c2d29e749fdd317f231489401601c82191552bf 100644
+--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
++++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
+@@ -493,14 +493,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 - 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();
+@@ -518,10 +520,20 @@ public record SerializableChunkData(
+ continue;
+ }
+
++ // Leaf start - Optimize chunkUnload
++ 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);
++ }
++ // 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 // Leaf - Optimize chunkUnload
+ );
+
+ if (blockNibble != null) {
+@@ -532,12 +544,16 @@ public record SerializableChunkData(
+ ((ca.spottedleaf.moonrise.patches.starlight.storage.StarlightSectionData)(Object)sectionData).starlight$setSkyLightState(skyNibble.state);
+ }
+
+- sectionsList.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();
+
++ if (blockEntityCount > 0) // Leaf - Optimize chunkUnload
+ for (BlockPos blockPos : chunk.getBlockEntitiesPos()) {
+ CompoundTag blockEntityNbtForSaving = chunk.getBlockEntityNbtForSaving(blockPos, level.registryAccess());
+ if (blockEntityNbtForSaving != null) {
+@@ -545,15 +561,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));
++ list2.addAll(protoChunk.getEntities());
++ } 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(); // Leaf - Optimize chunkUnload
+ }
+
+ Map map = new EnumMap<>(Heightmap.Types.class);
+@@ -561,14 +589,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)); // Leaf - Optimize chunkUnload
+ }
+ }
+
+ 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 - Optimize chunkUnload - remove stream
++ 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);
++ }
++ }
++ }
++ // 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/0240-Optimize-BlockEntityType-isValid.patch b/leaf-server/minecraft-patches/features/0240-Optimize-BlockEntityType-isValid.patch
new file mode 100644
index 00000000..5077d8d6
--- /dev/null
+++ b/leaf-server/minecraft-patches/features/0240-Optimize-BlockEntityType-isValid.patch
@@ -0,0 +1,46 @@
+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 386e6a48701b4c9256e33174123381a93d61e292..2bc620ceb8368e9a74b831de698de94ac9c47c3b 100644
+--- a/net/minecraft/world/level/block/entity/BlockEntityType.java
++++ b/net/minecraft/world/level/block/entity/BlockEntityType.java
+@@ -256,6 +256,14 @@ public class BlockEntityType {
+ private BlockEntityType(BlockEntityType.BlockEntitySupplier extends T> factory, Set validBlocks) {
+ this.factory = factory;
+ this.validBlocks = validBlocks;
++ // 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 - Optimize BlockEntityType#isValid
+ }
+
+ public T create(BlockPos pos, BlockState state) {
+@@ -263,7 +271,7 @@ public class BlockEntityType {
+ }
+
+ public boolean isValid(BlockState state) {
+- return this.validBlocks.contains(state.getBlock());
++ 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 3219b9caa654c7a64e5e4a92178528e1104b34c7..6d522e9485fadd6fc0f350cb30ba5224aa046d4f 100644
+--- a/net/minecraft/world/level/block/state/BlockBehaviour.java
++++ b/net/minecraft/world/level/block/state/BlockBehaviour.java
+@@ -101,6 +101,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 - Optimize BlockEntityType#isValid
+
+ public BlockBehaviour(BlockBehaviour.Properties properties) {
+ this.hasCollision = properties.hasCollision;
diff --git a/leaf-server/minecraft-patches/features/0241-PaperPR-Add-ticket-on-player-join-to-avoid-chunk-loa.patch b/leaf-server/minecraft-patches/features/0241-PaperPR-Add-ticket-on-player-join-to-avoid-chunk-loa.patch
new file mode 100644
index 00000000..de48f0f8
--- /dev/null
+++ b/leaf-server/minecraft-patches/features/0241-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 67275e803e0287306b163f4eec17388b9c701a8c..4344e5d9eda940849352e08734f95de2036bd87a 100644
+--- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
++++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
+@@ -48,6 +48,7 @@ public final class RegionizedPlayerChunkLoader {
+
+ public static final TicketType PLAYER_TICKET = ChunkSystemTicketType.create("chunk_system:player_ticket", Long::compareTo);
+ public static final TicketType PLAYER_TICKET_DELAYED = ChunkSystemTicketType.create("chunk_system:player_ticket_delayed", Long::compareTo, 5L * 20L);
++ public static final TicketType PLAYER_JOIN = ChunkSystemTicketType.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 GENERATED_TICKET_LEVEL = ChunkHolderManager.FULL_LOADED_TICKET_LEVEL;
+ public static final int LOADED_TICKET_LEVEL = ChunkTaskScheduler.getTicketLevel(ChunkStatus.EMPTY);
+diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
+index 07dafa0e12fd51e4b9e968e22b20f000d04f6dbc..8173242d149709f092e6d609f6e1d831eca0a884 100644
+--- a/net/minecraft/server/players/PlayerList.java
++++ b/net/minecraft/server/players/PlayerList.java
+@@ -330,6 +330,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/0242-paw-optimization.patch b/leaf-server/minecraft-patches/features/0242-paw-optimization.patch
new file mode 100644
index 00000000..c7898780
--- /dev/null
+++ b/leaf-server/minecraft-patches/features/0242-paw-optimization.patch
@@ -0,0 +1,199 @@
+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 f89fb321e50338e7765476cb5d7bdf2f02a497b3..83fcef85ed2a5410275e4419e4356994016f21d1 100644
+--- a/net/minecraft/server/level/ServerChunkCache.java
++++ b/net/minecraft/server/level/ServerChunkCache.java
+@@ -622,8 +622,10 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon
+ try {
+ this.chunkMap.collectSpawningChunks(list);
+ // Paper start - chunk tick iteration optimisation
+- this.shuffleRandom.setSeed(this.level.random.nextLong());
+- if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) Util.shuffle(list, this.shuffleRandom); // Paper - Optional per player mob spawns; do not need this when per-player is enabled
++ 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
+
+ for (LevelChunk levelChunk : list) {
+diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
+index 8ade900d016026cde482ccbca7a411993d9eadd9..414a70c352263a1cd2bfb938053990ea2be2b2c3 100644
+--- a/net/minecraft/server/level/ServerLevel.java
++++ b/net/minecraft/server/level/ServerLevel.java
+@@ -1367,13 +1367,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
+@@ -1386,13 +1380,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 960ae487a5a88a5fe9899c16b9553cea0fbfba37..1ff8522b81eb4bc6297b44a1f2a48c8021104185 100644
+--- a/net/minecraft/world/entity/Entity.java
++++ b/net/minecraft/world/entity/Entity.java
+@@ -1138,16 +1138,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
+-
+- @Nullable
+- private Vec3 moveVector;
+- private double moveStartX;
+- private double moveStartY;
+- private double 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)) {
+@@ -1155,16 +1145,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 {
+@@ -1298,13 +1279,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) {
+@@ -4771,9 +4745,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) {
+@@ -4881,9 +4853,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);
diff --git a/leaf-server/minecraft-patches/features/0243-Sakura-copy-EntityList-implementation-to-BasicEntity.patch b/leaf-server/minecraft-patches/features/0243-Sakura-copy-EntityList-implementation-to-BasicEntity.patch
new file mode 100644
index 00000000..acd34d0c
--- /dev/null
+++ b/leaf-server/minecraft-patches/features/0243-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 7b686d834e4eb36be5758b0e0a846a70d1e2294b..37930d1b87378ac3e8c7f5ebd79148bb66771f48 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/paper-patches/features/0008-Pufferfish-Optimize-mob-spawning.patch b/leaf-server/paper-patches/features/0008-Pufferfish-Optimize-mob-spawning.patch
index e3a54a79..cc4f9f8e 100644
--- a/leaf-server/paper-patches/features/0008-Pufferfish-Optimize-mob-spawning.patch
+++ b/leaf-server/paper-patches/features/0008-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 ece6db7b9a0dfd535141c0c756947c4898140503..82738ad42853e328532d854e3369e6a475ad729d 100644
+index ece6db7b9a0dfd535141c0c756947c4898140503..13df153d7b63a5c36a2fd14d3292e7eb32a394df 100644
--- a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java
+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java
@@ -11,7 +11,7 @@ public final class IteratorSafeOrderedReferenceSet {
@@ -31,7 +31,7 @@ index ece6db7b9a0dfd535141c0c756947c4898140503..82738ad42853e328532d854e3369e6a4
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,7 @@ index ece6db7b9a0dfd535141c0c756947c4898140503..82738ad42853e328532d854e3369e6a4
public IteratorSafeOrderedReferenceSet() {
this(Object.class);
-@@ -99,7 +99,7 @@ public final class IteratorSafeOrderedReferenceSet {
+@@ -99,11 +99,11 @@ public final class IteratorSafeOrderedReferenceSet {
}
public int createRawIterator() {
@@ -53,6 +53,11 @@ index ece6db7b9a0dfd535141c0c756947c4898140503..82738ad42853e328532d854e3369e6a4
if (this.indexMap.isEmpty()) {
return Integer.MAX_VALUE;
} else {
+- 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
+ }
+ }
+
@@ -120,7 +120,7 @@ public final class IteratorSafeOrderedReferenceSet {
}
@@ -62,7 +67,19 @@ index ece6db7b9a0dfd535141c0c756947c4898140503..82738ad42853e328532d854e3369e6a4
if (this.getFragFactor() >= this.maxFragFactor) {
this.defrag();
}
-@@ -137,7 +137,7 @@ public final class IteratorSafeOrderedReferenceSet {
+@@ -130,14 +130,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 +88,49 @@ index ece6db7b9a0dfd535141c0c756947c4898140503..82738ad42853e328532d854e3369e6a4
this.defrag();
}
//this.check();
-@@ -235,7 +235,7 @@ public final class IteratorSafeOrderedReferenceSet {
+@@ -169,14 +172,16 @@ 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
+ }
+
+ 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;
+ }
+@@ -186,11 +191,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
+@@ -221,7 +226,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();
+ }
+
+@@ -235,7 +240,7 @@ public final class IteratorSafeOrderedReferenceSet {
}
public IteratorSafeOrderedReferenceSet.Iterator iterator(final int flags) {
@@ -80,3 +139,12 @@ index ece6db7b9a0dfd535141c0c756947c4898140503..82738ad42853e328532d854e3369e6a4
return new BaseIterator<>(this, true, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize);
}
+@@ -322,7 +327,7 @@ public final class IteratorSafeOrderedReferenceSet {
+ }
+ this.lastReturned = null;
+ this.finished = true;
+- this.set.finishRawIterator();
++ this.set.finishRawIterator(); // Pufferfish - async mob spawning - diff on change
+ }
+ }
+ }
diff --git a/leaf-server/paper-patches/features/0048-PlayerInventoryOverflowEvent.patch b/leaf-server/paper-patches/features/0048-PlayerInventoryOverflowEvent.patch
index 465195d7..75335a21 100644
--- a/leaf-server/paper-patches/features/0048-PlayerInventoryOverflowEvent.patch
+++ b/leaf-server/paper-patches/features/0048-PlayerInventoryOverflowEvent.patch
@@ -5,23 +5,56 @@ 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 19180c08f41db939c1a9f0caeb62e5beb1117f69..c765118189cbf8db7291c1d50214a7f31acc3a49 100644
+index 19180c08f41db939c1a9f0caeb62e5beb1117f69..9b5c8c4c39657c18e67ad654bd0e5a30c09232c6 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,49 @@ public class CraftInventory implements Inventory {
}
}
}
+
+ // Leaf start - PlayerInventoryOverflowEvent
-+ if (org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent.getHandlerList().getRegisteredListeners().length > 0
++ 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/async/ai/AsyncGoalExecutor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java
new file mode 100644
index 00000000..b6a4c632
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java
@@ -0,0 +1,87 @@
+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.concurrent.locks.LockSupport;
+
+public class AsyncGoalExecutor {
+
+ public static final Logger LOGGER = LogManager.getLogger("Leaf Async Goal");
+
+ protected final SpscIntQueue queue;
+ protected final SpscIntQueue wake;
+ private final AsyncGoalThread thread;
+ 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;
+ 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 mob)) {
+ return false;
+ }
+ mob.goalSelector.wake();
+ mob.targetSelector.wake();
+ return true;
+ }
+
+ public final void submit(int entityId) {
+ if (!this.queue.send(entityId)) {
+ int spinCount = 0;
+ while (!this.queue.send(entityId)) {
+ spinCount++;
+ // Unpark the thread after some spinning to help clear the queue
+ if (spinCount > SPIN_LIMIT) {
+ unpark();
+ spinCount = 0;
+ }
+ Thread.onSpinWait();
+ }
+ }
+ dirty = true;
+ }
+
+ public final void unpark() {
+ if (dirty) LockSupport.unpark(thread);
+ dirty = false;
+ }
+
+ 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;
+ boolean b = mob.goalSelector.poll();
+ if (a || b) {
+ submit(id);
+ }
+ }
+ 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
new file mode 100644
index 00000000..d3d236c2
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java
@@ -0,0 +1,57 @@
+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 {
+
+ private static final int SPIN_TRIES = 1000;
+
+ public AsyncGoalThread(final MinecraftServer server) {
+ super(() -> run(server), "Leaf Async Goal Thread");
+ this.setDaemon(false);
+ this.setUncaughtExceptionHandler(Util::onThreadException);
+ this.setPriority(Thread.NORM_PRIORITY - 1);
+ this.start();
+ }
+
+ private static void run(MinecraftServer server) {
+ int emptySpins = 0;
+
+ while (server.isRunning()) {
+ 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
+ }
+ }
+ }
+ }
+}
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..0f064e5e
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java
@@ -0,0 +1,18 @@
+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/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/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 extends Annotation> 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() {
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/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/AsyncMobSpawning.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java
index 931e5831..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
@@ -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() {
@@ -22,13 +22,16 @@ 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.
- 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..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
@@ -16,9 +16,27 @@ 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.
+ DISCARD: Newly submitted task will be dropped directly.""",
+ """
+ 当队列满时, 新提交的任务将使用以下策略处理.
+ FLUSH_ALL: 所有等待中的任务都将在主线程上运行.
+ CALLER_RUNS: 新提交的任务将在主线程上运行.
+ DISCARD: 新提交的任务会被直接丢弃."""
+ );
+ 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 +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(), 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/AsyncPlayerDataSave.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncPlayerDataSave.java
index 96c5ad15..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
@@ -10,17 +10,13 @@ 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!**
+ config.addCommentRegionBased(getBasePath(), """
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 961572c5..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
@@ -11,22 +11,40 @@ public class AsyncTargetFinding extends ConfigModules {
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-target-finding";
}
- @Experimental
public static boolean enabled = false;
- public static boolean asyncTargetFindingInitialized;
+ public static boolean alertOther = true;
+ public static boolean searchBlock = true;
+ public static boolean searchEntity = true;
+ 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.""",
"""
这会将昂贵的实体目标搜索计算移至后台线程, 同时在主线程上保持实际的实体验证.""");
- if (!asyncTargetFindingInitialized) {
- asyncTargetFindingInitialized = true;
- enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
+ 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", true);
+ searchEntity = config.getBoolean(getBasePath() + ".async-search-entity", true);
+ queueSize = config.getInt(getBasePath() + ".queue-size", 4096);
+
+ if (queueSize <= 0) {
+ queueSize = 4096;
+ }
+ if (!enabled) {
+ alertOther = false;
+ searchEntity = false;
+ searchBlock = false;
}
}
}
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..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
@@ -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() {
@@ -24,15 +25,22 @@ 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);
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..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
@@ -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 {
@@ -19,22 +20,29 @@ 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..""",
+ 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);
+ }
}
}
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.""",
+ """
+ 监听此物品栏物品溢出事件的完整类名."""));
+ }
+}
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/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() {
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/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 {
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() {
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..93430609
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/util/map/BlockPosIterator.java
@@ -0,0 +1,70 @@
+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 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;
+ }
+ }
+}
diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/map/ConcurrentLongHashSet.java b/leaf-server/src/main/java/org/dreeam/leaf/util/map/ConcurrentLongHashSet.java
index b699f723..4d9fea75 100644
--- a/leaf-server/src/main/java/org/dreeam/leaf/util/map/ConcurrentLongHashSet.java
+++ b/leaf-server/src/main/java/org/dreeam/leaf/util/map/ConcurrentLongHashSet.java
@@ -1,6 +1,9 @@
package org.dreeam.leaf.util.map;
-import it.unimi.dsi.fastutil.longs.*;
+import it.unimi.dsi.fastutil.longs.LongCollection;
+import it.unimi.dsi.fastutil.longs.LongIterator;
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+import it.unimi.dsi.fastutil.longs.LongSet;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
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..27698be1
--- /dev/null
+++ b/leaf-server/src/main/java/org/dreeam/leaf/util/map/spottedleaf/LeafConcurrentLong2ReferenceChainedHashTable.java
@@ -0,0 +1,2294 @@
+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.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.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.
+ */
+ 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 (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;
+ V oldValue;
+ TableEntry[] currentTable = this.table;
+
+ table_loop:
+ for (;;) {
+ final int tableLength = currentTable.length;
+ // Init check
+ if (tableLength == 0) {
+ currentTable = this.table;
+ if (currentTable.length == 0) 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 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 {
+ continue table_loop; // Should not happen if head was non-null/non-marker. Retry.
+ }
+ } // 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;
+ 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;
+ 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