diff --git a/.github/workflows/build-1215.yml b/.github/workflows/build-1215.yml index 1a172f97..660e3fd2 100644 --- a/.github/workflows/build-1215.yml +++ b/.github/workflows/build-1215.yml @@ -1,4 +1,5 @@ name: Build Leaf 1.21.5 + on: push: branches: [ "dev/1.21.5" ] @@ -10,8 +11,6 @@ jobs: env: BUILD_NUMBER: ${{ github.run_number }} GRADLE_MEMORY: "-Xmx4g -XX:MaxMetaspaceSize=2g" - outputs: - build_number: ${{ env.BUILD_NUMBER }} steps: - name: Checkout repository uses: actions/checkout@main @@ -82,16 +81,6 @@ jobs: - name: Create MojmapPaperclipJar run: ./gradlew createMojmapPaperclipJar --stacktrace --parallel --no-daemon - - 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 - - - name: Upload Leaf as build artifact - uses: actions/upload-artifact@main - with: - name: Leaf 1.21.5 - path: ./leaf-1.21.5-*.jar - - name: Prepare release notes and artifacts run: | chmod +x ./scripts/prepareRelease.sh @@ -101,6 +90,12 @@ jobs: GITHUB_REPO: ${{ github.repository }} BUILD_NUMBER: ${{ env.BUILD_NUMBER }} + - name: Upload Leaf + uses: actions/upload-artifact@main + with: + name: Leaf 1.21.5 + path: ./leaf-1.21.5-${{ env.BUILD_NUMBER }}.jar + - name: Release Leaf uses: softprops/action-gh-release@master with: @@ -112,3 +107,42 @@ jobs: target_commitish: "${{ github.sha }}" draft: false prerelease: true + + - name: Calculate SHA-256 + id: hash + run: | + FILE_NAME="leaf-1.21.5-${{ env.BUILD_NUMBER }}.jar" + HASH=$(sha256sum "$FILE_NAME" | awk '{ print $1 }') + echo "sha256=$HASH" >> $GITHUB_OUTPUT + - name: Upload JAR to download API + uses: appleboy/scp-action@master + with: + host: ${{ secrets.API_HOST }} + username: ${{ secrets.API_USER }} + password: ${{ secrets.API_PASS }} + source: "./leaf-1.21.5-${{ env.BUILD_NUMBER }}.jar" + target: "~/api/uploads/" + + - name: Insert build to download API + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.API_HOST }} + username: ${{ secrets.API_USER }} + password: ${{ secrets.API_PASS }} + script: | + BRANCH="${{ github.ref_name }}" + REPO_DIR="/root/Leaf-${BRANCH//\//-}" # Replace slashes with dashes to avoid directory issues + [ -d "$REPO_DIR/.git" ] && cd "$REPO_DIR" && git fetch origin && git checkout "$BRANCH" && git reset --hard "origin/$BRANCH" || git clone --branch "$BRANCH" --depth 1 https://github.com/Winds-Studio/Leaf "$REPO_DIR" + # Proceed to insert the build into the API + cd ~/api/cli + node insertBuild.js \ + --projectName leaf \ + --projectFriendlyName "Leaf" \ + --version 1.21.5 \ + --versionGroupName 1.21.5 \ + --versionName 1.21.5 \ + --build-number ${{ env.BUILD_NUMBER }} \ + --repositoryPath "$REPO_DIR" \ + --storagePath /root/api/storage \ + --download "primary:/root/api/uploads/leaf-1.21.5-${{ env.BUILD_NUMBER }}.jar:${{ steps.hash.outputs.sha256 }}" \ + --buildChannel default diff --git a/.github/workflows/publish-api.yml b/.github/workflows/publish-api.yml index 71bf488a..16783987 100644 --- a/.github/workflows/publish-api.yml +++ b/.github/workflows/publish-api.yml @@ -1,7 +1,7 @@ name: Publish API on: push: - branches: [ "ver/1.21.4" ] + branches: [ "dev/1.21.5" ] jobs: build: @@ -44,7 +44,7 @@ jobs: - name: Build run: ./gradlew build -x test - - name: Publish API + - name: Publish Maven API continue-on-error: true run: | ./gradlew :leaf-api:publish diff --git a/build-data/leaf.at b/build-data/leaf.at index 329dbff8..5638508d 100644 --- a/build-data/leaf.at +++ b/build-data/leaf.at @@ -4,6 +4,7 @@ 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.level.ServerEntity sendDirtyEntityData()V public net.minecraft.server.players.PlayerList SEND_PLAYER_INFO_INTERVAL public net.minecraft.util.Mth SIN public net.minecraft.world.entity.Entity blockPosition diff --git a/leaf-api/paper-patches/features/0014-Remove-Timings.patch b/leaf-api/paper-patches/features/0014-Remove-Timings.patch index 5ca2588e..5237e222 100644 --- a/leaf-api/paper-patches/features/0014-Remove-Timings.patch +++ b/leaf-api/paper-patches/features/0014-Remove-Timings.patch @@ -1277,60 +1277,39 @@ index df142a89b8c43acb81eb383eac0ef048a1f49a6e..00000000000000000000000000000000 - } -} diff --git a/src/main/java/co/aikar/timings/Timings.java b/src/main/java/co/aikar/timings/Timings.java -deleted file mode 100644 -index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..0000000000000000000000000000000000000000 +index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..908b610bfdfe911675748211b74b809d9e0af3e5 100644 --- a/src/main/java/co/aikar/timings/Timings.java -+++ /dev/null -@@ -1,325 +0,0 @@ --/* -- * This file is licensed under the MIT License (MIT). -- * -- * Copyright (c) 2014 Daniel Ennis -- * -- * Permission is hereby granted, free of charge, to any person obtaining a copy -- * of this software and associated documentation files (the "Software"), to deal -- * in the Software without restriction, including without limitation the rights -- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -- * copies of the Software, and to permit persons to whom the Software is -- * furnished to do so, subject to the following conditions: -- * -- * The above copyright notice and this permission notice shall be included in -- * all copies or substantial portions of the Software. -- * -- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -- * THE SOFTWARE. -- */ --package co.aikar.timings; -- ++++ b/src/main/java/co/aikar/timings/Timings.java +@@ -23,303 +23,48 @@ + */ + package co.aikar.timings; + -import com.google.common.base.Preconditions; -import com.google.common.collect.EvictingQueue; -import com.google.common.collect.Lists; --import net.kyori.adventure.text.Component; + import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.event.ClickEvent; -import net.kyori.adventure.text.format.TextColor; -import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -import org.bukkit.Bukkit; --import org.bukkit.command.CommandSender; + import org.bukkit.command.CommandSender; -import org.bukkit.plugin.Plugin; -- + -import java.util.List; -import java.util.Queue; -import java.util.logging.Level; -import org.jetbrains.annotations.NotNull; --import org.jetbrains.annotations.Nullable; -- --/** -- * @deprecated Timings will be removed in the future -- */ --@Deprecated(forRemoval = true) + import org.jetbrains.annotations.Nullable; + ++// Leaf start - Remove Timings + /** + * @deprecated Timings will be removed in the future ++ * Keep this class for plugin compatibility (detect usage) + */ + @Deprecated(forRemoval = true) -@SuppressWarnings({"UnusedDeclaration", "WeakerAccess", "SameParameterValue"}) --public final class Timings { -- + public final class Timings { + - final static List requestingReport = Lists.newArrayList(); - private static final int MAX_HISTORY_FRAMES = 12; - public static final Timing NULL_HANDLER = new NullTimingHandler(); @@ -1340,8 +1319,8 @@ index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..00000000000000000000000000000000 - private static int historyLength = -1; - private static boolean warnedAboutDeprecationOnEnable; - -- private Timings() {} -- + private Timings() {} + - /** - * Returns a Timing for a plugin corresponding to a name. - * @@ -1417,9 +1396,10 @@ index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..00000000000000000000000000000000 - * - * @return Enabled or not - */ -- public static boolean isTimingsEnabled() { + public static boolean isTimingsEnabled() { - return timingsEnabled; -- } ++ return false; + } - - /** - *

Sets whether or not the Spigot Timings system should be enabled

@@ -1428,14 +1408,14 @@ index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..00000000000000000000000000000000 - * - * @param enabled Should timings be reported - */ -- public static void setTimingsEnabled(boolean enabled) { + public static void setTimingsEnabled(boolean enabled) { - if (enabled && !warnedAboutDeprecationOnEnable) { - Bukkit.getLogger().severe(PlainTextComponentSerializer.plainText().serialize(deprecationMessage())); - warnedAboutDeprecationOnEnable = true; - } -- } + } - -- public static Component deprecationMessage() { + public static Component deprecationMessage() { - return Component.text() - .color(TextColor.color(0xffc93a)) - .append(Component.text("[!] The timings profiler is in no-op mode and will be fully removed in a later update.")) @@ -1450,7 +1430,8 @@ index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..00000000000000000000000000000000 - .clickEvent(ClickEvent.openUrl("https://github.com/PaperMC/Paper/discussions/10565"))) - ) - .build(); -- } ++ return Component.empty(); + } - - /** - *

Sets whether or not the Timings should monitor at Verbose level.

@@ -1459,9 +1440,10 @@ index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..00000000000000000000000000000000 - * - * @return Enabled or not - */ -- public static boolean isVerboseTimingsEnabled() { + public static boolean isVerboseTimingsEnabled() { - return verboseEnabled; -- } ++ return false; + } - - /** - *

Sets whether or not the Timings should monitor at Verbose level.

@@ -1471,10 +1453,10 @@ index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..00000000000000000000000000000000 - * - * @param enabled Should high-frequency timings be reported - */ -- public static void setVerboseTimingsEnabled(boolean enabled) { + public static void setVerboseTimingsEnabled(boolean enabled) { - verboseEnabled = enabled; - TimingsManager.needsRecheckEnabled = true; -- } + } - - /** - *

Gets the interval between Timing History report generation.

@@ -1483,9 +1465,10 @@ index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..00000000000000000000000000000000 - * - * @return Interval in ticks - */ -- public static int getHistoryInterval() { + public static int getHistoryInterval() { - return historyInterval; -- } ++ return 0; + } - - /** - *

Sets the interval between Timing History report generations.

@@ -1497,13 +1480,13 @@ index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..00000000000000000000000000000000 - * - * @param interval Interval in ticks - */ -- public static void setHistoryInterval(int interval) { + public static void setHistoryInterval(int interval) { - historyInterval = Math.max(20*60, interval); - // Recheck the history length with the new Interval - if (historyLength != -1) { - setHistoryLength(historyLength); - } -- } + } - - /** - * Gets how long in ticks Timings history is kept for the server. @@ -1512,9 +1495,10 @@ index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..00000000000000000000000000000000 - * - * @return Duration in Ticks - */ -- public static int getHistoryLength() { + public static int getHistoryLength() { - return historyLength; -- } ++ return 0; + } - - /** - * Sets how long Timing History reports are kept for the server. @@ -1527,7 +1511,7 @@ index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..00000000000000000000000000000000 - * - * @param length Duration in ticks - */ -- public static void setHistoryLength(int length) { + public static void setHistoryLength(int length) { - // Cap at 12 History Frames, 1 hour at 5 minute frames. - int maxLength = historyInterval * MAX_HISTORY_FRAMES; - // For special cases of servers with special permission to bypass the max. @@ -1544,14 +1528,14 @@ index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..00000000000000000000000000000000 - } - TimingsManager.HISTORY = EvictingQueue.create(frames); - TimingsManager.HISTORY.addAll(oldQueue); -- } + } - - /** - * Resets all Timing Data - */ -- public static void reset() { + public static void reset() { - TimingsManager.reset(); -- } + } - - /** - * Generates a report and sends it to the specified command sender. @@ -1559,7 +1543,7 @@ index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..00000000000000000000000000000000 - * If sender is null, ConsoleCommandSender will be used. - * @param sender The sender to send to, or null to use the ConsoleCommandSender - */ -- public static void generateReport(@Nullable CommandSender sender) { + public static void generateReport(@Nullable CommandSender sender) { - if (sender == null) { - sender = Bukkit.getConsoleSender(); - } @@ -1604,9 +1588,10 @@ index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..00000000000000000000000000000000 - @NotNull - static TimingHandler ofSafe(@Nullable String groupName, @NotNull String name, @Nullable Timing groupHandler) { - return TimingsManager.getHandler(groupName, name, groupHandler); -- } --} -- + } ++ // Leaf end - Remove Timings + } + diff --git a/src/main/java/co/aikar/timings/TimingsCommand.java b/src/main/java/co/aikar/timings/TimingsCommand.java deleted file mode 100644 index b83e5ff7ada8771fdf27ba9807c77ba6a4ce12da..0000000000000000000000000000000000000000 @@ -2831,37 +2816,6 @@ index 3e61a926620a67daec3af54b72a1b911eaef2ed4..00000000000000000000000000000000 - return new MRUMapCache(map); - } -} -diff --git a/src/main/java/org/bukkit/command/BufferedCommandSender.java b/src/main/java/org/bukkit/command/BufferedCommandSender.java -deleted file mode 100644 -index 45ed63797b13e114bf3795c80a6c3967d8eb2351..0000000000000000000000000000000000000000 ---- a/src/main/java/org/bukkit/command/BufferedCommandSender.java -+++ /dev/null -@@ -1,25 +0,0 @@ --package org.bukkit.command; -- --import org.jetbrains.annotations.NotNull; -- --/** -- * @deprecated Timings will be removed in the future -- */ --@Deprecated(forRemoval = true) --public class BufferedCommandSender implements MessageCommandSender { -- private final StringBuffer buffer = new StringBuffer(); -- @Override -- public void sendMessage(@NotNull String message) { -- buffer.append(message); -- buffer.append("\n"); -- } -- -- @NotNull -- public String getBuffer() { -- return buffer.toString(); -- } -- -- public void reset() { -- this.buffer.setLength(0); -- } --} diff --git a/src/main/java/org/bukkit/command/Command.java b/src/main/java/org/bukkit/command/Command.java index 27a7c69f23084e821d945d5e97e51a94ddd94e58..d645ee8470a2dd9f7b8eff2b7ff2211aba9c342f 100644 --- a/src/main/java/org/bukkit/command/Command.java @@ -3017,38 +2971,15 @@ index ce45dbc7b02c405f1b3460563cf87e7888702ba9..c43ce5b7bed3297cd655a7f2ed4b4717 } return ret; diff --git a/src/main/java/org/spigotmc/CustomTimingsHandler.java b/src/main/java/org/spigotmc/CustomTimingsHandler.java -deleted file mode 100644 -index 5fbacfcf108432c5187aa9a4092d00d7d5b0fd53..0000000000000000000000000000000000000000 +index 5fbacfcf108432c5187aa9a4092d00d7d5b0fd53..68e2a332e512a4aea45b8fb33a6e34431548d3be 100644 --- a/src/main/java/org/spigotmc/CustomTimingsHandler.java -+++ /dev/null -@@ -1,67 +0,0 @@ --/* -- * This file is licensed under the MIT License (MIT). -- * -- * Copyright (c) 2014 Daniel Ennis -- * -- * Permission is hereby granted, free of charge, to any person obtaining a copy -- * of this software and associated documentation files (the "Software"), to deal -- * in the Software without restriction, including without limitation the rights -- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -- * copies of the Software, and to permit persons to whom the Software is -- * furnished to do so, subject to the following conditions: -- * -- * The above copyright notice and this permission notice shall be included in -- * all copies or substantial portions of the Software. -- * -- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -- * THE SOFTWARE. -- */ --package org.spigotmc; -- ++++ b/src/main/java/org/spigotmc/CustomTimingsHandler.java +@@ -23,45 +23,20 @@ + */ + package org.spigotmc; + -import org.bukkit.Bukkit; --import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.NotNull; -import org.bukkit.plugin.AuthorNagException; -import co.aikar.timings.Timing; -import co.aikar.timings.Timings; @@ -3056,20 +2987,23 @@ index 5fbacfcf108432c5187aa9a4092d00d7d5b0fd53..00000000000000000000000000000000 - -import java.lang.reflect.Method; -import java.util.logging.Level; -- --/** + ++// Leaf start - Remove Timings + /** - * This is here for legacy purposes incase any plugin used it. - * - * If you use this, migrate ASAP as this will be removed in the future! - * - * @deprecated - * @see co.aikar.timings.Timings#of -- */ --@Deprecated(forRemoval = true) --public final class CustomTimingsHandler { ++ * @deprecated Timings will be removed in the future ++ * Keep this class for plugin compatibility (detect usage) + */ + @Deprecated(forRemoval = true) + public final class CustomTimingsHandler { - private final Timing handler; -- -- public CustomTimingsHandler(@NotNull String name) { + + public CustomTimingsHandler(@NotNull String name) { - Timing timing; - - new AuthorNagException("Deprecated use of CustomTimingsHandler. Timings has been removed.").printStackTrace(); @@ -3083,12 +3017,15 @@ index 5fbacfcf108432c5187aa9a4092d00d7d5b0fd53..00000000000000000000000000000000 - timing = Timings.NULL_HANDLER; - } - handler = timing; -- } -- + } + - public void startTiming() { handler.startTiming(); } - public void stopTiming() { handler.stopTiming(); } - --} ++ public void startTiming() {} ++ public void stopTiming() {} ++ // Leaf end - Remove Timings + } diff --git a/src/test/java/org/bukkit/AnnotationTest.java b/src/test/java/org/bukkit/AnnotationTest.java index 37feafd626aaa17aba888d7ff13728b3c6f26d4d..e42619418c1a3e3dac22e7310bb9d64b42b9f6a7 100644 --- a/src/test/java/org/bukkit/AnnotationTest.java diff --git a/leaf-archived-patches/removed/hardfork/server/0166-Save-world-async-properly.patch b/leaf-archived-patches/removed/hardfork/server/0166-Save-world-async-properly.patch new file mode 100644 index 00000000..c74c0fa4 --- /dev/null +++ b/leaf-archived-patches/removed/hardfork/server/0166-Save-world-async-properly.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Thu, 8 May 2025 00:05:01 +0200 +Subject: [PATCH] Save world async properly + +Removed since Paper 1.21.4/5, added on Paper side + +P.S from Tai: I've been using this fix for weeks in my own server but didn't had balls to push it as thought it may cause issues but, it's merged in paper 1.21.5 now. + +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index ba1dd51e7187a80e8438e46383257c22f5382130..6cb0c14cb7aa243bbee6ca9ba57da4cc6eafdfd8 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -1439,7 +1439,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + if (doFull) { +- this.saveLevelData(true); ++ this.saveLevelData(false); // Leaf - Save world async properly + } + // chunk autosave is already called by the ChunkSystem during unload processing (ChunkMap#processUnloads) + // Copied from save() diff --git a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0079-Hide-specified-item-components.patch b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0079-Hide-specified-item-components.patch index 14389691..59eb1838 100644 --- a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0079-Hide-specified-item-components.patch +++ b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0079-Hide-specified-item-components.patch @@ -33,18 +33,18 @@ index c1130f596cf3443eeb62eb1b12587172fe0859ee..18590e0b1d94ee3266637c5f3ab65ead @Override diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java -index e6419715fab462b12790ecb175ce1e1a1fceed8f..8a0d1aebad1f92c43112e279b9c5922fdd1fd432 100644 +index 3dcd8df0b395a8fed8bc0cbe0ff78f4ae0056fd3..cee7daa4908efde754442bf7ef0932b94cf5ebca 100644 --- a/net/minecraft/world/inventory/AbstractContainerMenu.java +++ b/net/minecraft/world/inventory/AbstractContainerMenu.java @@ -306,7 +306,12 @@ public abstract class AbstractContainerMenu { private void synchronizeCarriedToRemote() { if (!this.suppressRemoteUpdates) { -- if (!this.matchesRemote(this.getCarried(), this.remoteCarried)) { // Paper - add flag to simplify remote matching logic +- if (!ItemStack.matches(this.getCarried(), this.remoteCarried)) { + // Leaf start - Hide specified item components - Avoid some frequent client animations + final boolean matchResult = org.dreeam.leaf.config.modules.gameplay.HideItemComponent.enabled + ? !org.dreeam.leaf.util.item.ItemStackStripper.matchesStripped(this.getCarried(), this.remoteCarried) -+ : !this.matchesRemote(this.getCarried(), this.remoteCarried); // Paper - add flag to simplify remote matching logic ++ : !ItemStack.matches(this.getCarried(), this.remoteCarried); + if (matchResult) { + // Leaf end - Hide specified item components - Avoid some frequent client animations this.remoteCarried = this.getCarried().copy(); diff --git a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch index 35e26162..ff920aca 100644 --- a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch +++ b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch @@ -568,7 +568,7 @@ index d4048661575ebfaf128ba25da365843774364e0e..33dd16a26edd2974f04d9a868d3e58e8 // Gale start - Pufferfish - SIMD support diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index ae5d3de44fb710b48fdabf04f5e706df1f9889b7..31abf2da10bc9b4b7825ed4b3d4e9da52feb2e39 100644 +index a66e5f6652d9633c856490de36d8d8fdf8a5298a..60e0296312030d25f917c568c17ce86d08e18122 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -182,7 +182,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -650,10 +650,10 @@ 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 fefaab58da149b082a4d1e3bed9ec84ae8488d45..9100da3fe4e478cea7198cb4e028fcefccb3eb3c 100644 +index 8362def0dc61496a087bd859052bd80ebba83185..09f517059aa47ca67329bc913243d4fdee09abe5 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 +@@ -427,6 +427,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc return this.viewDistanceHolder; } // Paper end - rewrite chunk system @@ -661,7 +661,7 @@ index fefaab58da149b082a4d1e3bed9ec84ae8488d45..9100da3fe4e478cea7198cb4e028fcef public ServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) { super(level, level.getSharedSpawnPos(), level.getSharedSpawnAngle(), gameProfile); -@@ -810,6 +811,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -803,6 +804,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc @Override public void tick() { @@ -669,7 +669,7 @@ index fefaab58da149b082a4d1e3bed9ec84ae8488d45..9100da3fe4e478cea7198cb4e028fcef // CraftBukkit start if (this.joining) { this.joining = false; -@@ -1455,6 +1457,8 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -1448,6 +1450,8 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc teleportTransition.postTeleportTransition().onTransition(this); return this; } else { @@ -678,7 +678,7 @@ index fefaab58da149b082a4d1e3bed9ec84ae8488d45..9100da3fe4e478cea7198cb4e028fcef // CraftBukkit start /* this.isChangingDimension = true; -@@ -1826,6 +1830,12 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -1819,6 +1823,12 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc return OptionalInt.empty(); } else { // CraftBukkit start @@ -691,7 +691,7 @@ index fefaab58da149b082a4d1e3bed9ec84ae8488d45..9100da3fe4e478cea7198cb4e028fcef this.containerMenu = abstractContainerMenu; // Moved up if (!this.isImmobile()) this.connection -@@ -1890,6 +1900,11 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -1883,6 +1893,11 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc } @Override public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { @@ -704,7 +704,7 @@ index fefaab58da149b082a4d1e3bed9ec84ae8488d45..9100da3fe4e478cea7198cb4e028fcef // Paper end - Inventory close reason this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java -index b17c8a2f5294ac28cc05fb05c84a041b2c6c8721..0b8b4658dbbad1bacc13e97b4fc0cdcea7e36a06 100644 +index b17c8a2f5294ac28cc05fb05c84a041b2c6c8721..3591de34443069f3f163f8d17df6372c3068611d 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java @@ -252,6 +252,8 @@ public abstract class PlayerList { @@ -716,14 +716,12 @@ index b17c8a2f5294ac28cc05fb05c84a041b2c6c8721..0b8b4658dbbad1bacc13e97b4fc0cdce player.isRealPlayer = true; // Paper player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed GameProfile gameProfile = player.getGameProfile(); -@@ -891,6 +893,17 @@ public abstract class PlayerList { +@@ -891,6 +893,15 @@ public abstract class PlayerList { return this.respawn(player, keepInventory, reason, eventReason, null); } public ServerPlayer respawn(ServerPlayer player, boolean keepInventory, Entity.RemovalReason reason, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason eventReason, org.bukkit.Location location) { + // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) + if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { -+ // SparklyPaper - parallel world ticking (additional concurrency issues logs) -+ System.out.println("respawning player - current player container is " + player.containerMenu + " but their inventory is " + player.inventoryMenu); + if (location != null) // Leaf - THIS CAN BE NULL; see PlayerList::respawn(ServerPlayer, boolean, Entity.RemovalReason, PlayerRespawnEvent.RespawnReason) + ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, from world " + player.serverLevel().getWorld().getName() + " to world " + location.getWorld().getName()); + else @@ -734,7 +732,7 @@ index b17c8a2f5294ac28cc05fb05c84a041b2c6c8721..0b8b4658dbbad1bacc13e97b4fc0cdce player.stopRiding(); // CraftBukkit this.players.remove(player); this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot -@@ -902,6 +915,7 @@ public abstract class PlayerList { +@@ -902,6 +913,7 @@ public abstract class PlayerList { ServerPlayer serverPlayer = player; Level fromWorld = player.level(); player.wonGame = false; @@ -871,32 +869,48 @@ index 88b07fbb96b20124777889830afa480673629d43..f8bb32840129e57b7799f883cb4570d2 + // Leaf end - SparklyPaper - parallel world ticking mod (prevent clearing portal process) } diff --git a/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java b/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java -index 3614551856c594f3c0cfee984fcf03fad672b007..1a41dd00a1ea4d0587d833c85545baf1b5f660d5 100644 +index 3614551856c594f3c0cfee984fcf03fad672b007..f4577f908ca9f279b72d89e5b0822d34b6fb7dd1 100644 --- a/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java +++ b/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java -@@ -46,12 +46,20 @@ public class GoToPotentialJobSite extends Behavior { +@@ -44,14 +44,34 @@ public class GoToPotentialJobSite extends Behavior { + Optional memory = entity.getBrain().getMemory(MemoryModuleType.POTENTIAL_JOB_SITE); + memory.ifPresent(globalPos -> { BlockPos blockPos = globalPos.pos(); - ServerLevel level1 = level.getServer().getLevel(globalPos.dimension()); - if (level1 != null) { +- ServerLevel level1 = level.getServer().getLevel(globalPos.dimension()); +- if (level1 != null) { - PoiManager poiManager = level1.getPoiManager(); - if (poiManager.exists(blockPos, holder -> true)) { - poiManager.release(blockPos); - } -+ // Leaf start - SparklyPaper - parallel world ticking mod (handling for navigating to potential job site cross-dimension) -+ Runnable releasePoiTask = () -> { -+ PoiManager poiManager = level1.getPoiManager(); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel entityLevel = level; // Villager's current level ++ ServerLevel poiLevel = entityLevel.getServer().getLevel(globalPos.dimension()); // POI's actual level ++ ++ if (poiLevel != null) { ++ Runnable poiOperationsTask = () -> { ++ PoiManager poiManager = poiLevel.getPoiManager(); + if (poiManager.exists(blockPos, holder -> true)) { + poiManager.release(blockPos); + } ++ }; ++ ++ // DebugPackets.sendPoiTicketCountPacket uses the entity's level for its PoiManager context. ++ Runnable debugPacketTask = () -> { ++ DebugPackets.sendPoiTicketCountPacket(entityLevel, blockPos); ++ }; - DebugPackets.sendPoiTicketCountPacket(level, blockPos); -+ DebugPackets.sendPoiTicketCountPacket(level, blockPos); -+ }; -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) -+ level.moonrise$getChunkTaskScheduler().scheduleChunkTask(0, 0, releasePoiTask, ca.spottedleaf.concurrentutil.util.Priority.BLOCKING); -+ else -+ releasePoiTask.run(); -+ // Leaf end - SparklyPaper - parallel world ticking mod (handling for navigating to potential job site cross-dimension) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { // Added curly braces here ++ // Schedule POI operations on the POI's level thread, using POI's chunk coordinates for locality ++ poiLevel.moonrise$getChunkTaskScheduler().scheduleChunkTask(blockPos.getX() >> 4, blockPos.getZ() >> 4, poiOperationsTask, ca.spottedleaf.concurrentutil.util.Priority.BLOCKING); ++ // Schedule debug packet on the entity's level thread, using entity's chunk coordinates for locality ++ entityLevel.moonrise$getChunkTaskScheduler().scheduleChunkTask(entity.chunkPosition().x, entity.chunkPosition().z, debugPacketTask, ca.spottedleaf.concurrentutil.util.Priority.BLOCKING); ++ } ++ else { // PWT disabled, run inline on current (entity's) thread ++ poiOperationsTask.run(); // This will use poiLevel's PoiManager but thread checks are permissive ++ debugPacketTask.run(); // This will use entityLevel's PoiManager ++ } ++ // Leaf end - SparklyPaper - parallel world ticking } }); entity.getBrain().eraseMemory(MemoryModuleType.POTENTIAL_JOB_SITE); @@ -1017,7 +1031,7 @@ index d212f57c8c0b2086f567fd30237b110203d9e8cb..ed4df82581b5411e54068ccc59ea85a7 } else { Entity entity = owner.teleport( diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java -index 8a0d1aebad1f92c43112e279b9c5922fdd1fd432..d4fc9466d61a680b85859965a8f7dc795b8c7130 100644 +index cee7daa4908efde754442bf7ef0932b94cf5ebca..ff2ff95ec9d94e2e31e8174196b384c37d56f38a 100644 --- a/net/minecraft/world/inventory/AbstractContainerMenu.java +++ b/net/minecraft/world/inventory/AbstractContainerMenu.java @@ -92,8 +92,14 @@ public abstract class AbstractContainerMenu { diff --git a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0140-SparklyPaper-Track-each-world-MSPT.patch b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0135-SparklyPaper-Track-each-world-MSPT.patch similarity index 98% rename from leaf-archived-patches/unapplied/server/minecraft-patches/features/0140-SparklyPaper-Track-each-world-MSPT.patch rename to leaf-archived-patches/unapplied/server/minecraft-patches/features/0135-SparklyPaper-Track-each-world-MSPT.patch index 2fb7a7cb..aef67f40 100644 --- a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0140-SparklyPaper-Track-each-world-MSPT.patch +++ b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0135-SparklyPaper-Track-each-world-MSPT.patch @@ -27,7 +27,7 @@ index ac751d460ae0c8dbb858c4047c459a11b57ae175..24926aa7ed5c78b235659daf18b224b1 CrashReport crashReport = CrashReport.forThrowable(levelTickingException, "Exception ticking world"); serverLevel.fillReportDetails(crashReport); diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 4ecb9a4125233f91379fd2792112aca6bbb3e33f..b5a61261083ddab70582c1a1d5cac0b9ced9b652 100644 +index 31abf2da10bc9b4b7825ed4b3d4e9da52feb2e39..9ba1c29b75ba0eb545097eef4fe568c53ebd885c 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -573,6 +573,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe diff --git a/leaf-archived-patches/unapplied/server/minecraft-patches/features/1167-Pluto-Check-if-the-cactus-can-even-survive-being-pla.patch b/leaf-archived-patches/unapplied/server/minecraft-patches/features/1167-Pluto-Check-if-the-cactus-can-even-survive-being-pla.patch new file mode 100644 index 00000000..b465fe70 --- /dev/null +++ b/leaf-archived-patches/unapplied/server/minecraft-patches/features/1167-Pluto-Check-if-the-cactus-can-even-survive-being-pla.patch @@ -0,0 +1,83 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Yive <6853318+Yive@users.noreply.github.com> +Date: Tue, 11 Jul 2023 13:27:01 -0700 +Subject: [PATCH] Pluto: Check if the cactus can even survive being placed + +Original license: GPLv3 +Original project: https://github.com/Yive/Pluto + +Results at 3,000 randomTickSpeed and 24,448 cacti: + +check-survival-before-growth - false & doTileDrop - true: 48mspt +check-survival-before-growth - true & doTileDrop - true: 25mspt +check-survival-before-growth - false & doTileDrop - false: 18mspt +check-survival-before-growth - true & doTileDrop - false: 6mspt + +Setting the gamerule "doTileDrop" to false was to simulate a server that has chunk collectors. + +Note: This might increase the item output of a cacti farm, though in theory it should act the same as vanilla. + +diff --git a/net/minecraft/world/level/block/CactusBlock.java b/net/minecraft/world/level/block/CactusBlock.java +index 079b4c95cf81119ca99daeb159aefca389afed74..8fe29455d7ae44f43c663718d38ea2d8cf639797 100644 +--- a/net/minecraft/world/level/block/CactusBlock.java ++++ b/net/minecraft/world/level/block/CactusBlock.java +@@ -49,10 +49,15 @@ public class CactusBlock extends Block implements BonemealableBlock { // Purpur + @Override + protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) { + BlockPos blockPos = pos.above(); +- if (level.isEmptyBlock(blockPos)) { ++ // Pluto start - Decrease chunk/block lookups ++ net.minecraft.world.level.chunk.LevelChunk chunk = level.getChunkIfLoaded(blockPos); ++ if (chunk == null) return; ++ ++ if (chunk.getBlockState(blockPos).isAir()) { ++ // Pluto end - Decrease chunk/block lookups + int i = 1; + +- while (level.getBlockState(pos.below(i)).is(this)) { ++ while (chunk.getBlockState(pos.below(i)).is(this)) { // Pluto - Decrease chunk/block lookups + i++; + } + +@@ -61,11 +66,28 @@ public class CactusBlock extends Block implements BonemealableBlock { // Purpur + + int modifier = level.spigotConfig.cactusModifier; // Spigot - SPIGOT-7159: Better modifier resolution + if (ageValue >= 15 || (modifier != 100 && random.nextFloat() < (modifier / (100.0f * 16)))) { // Spigot - SPIGOT-7159: Better modifier ++ // Pluto start - Check if the cactus can even survive being placed ++ if (org.dreeam.leaf.config.modules.opt.CheckSurvivalBeforeGrowth.cactusCheckSurvivalBeforeGrowth && !canSurvive(level, blockPos)) { ++ level.levelEvent(LevelEvent.PARTICLES_DESTROY_BLOCK, blockPos, Block.getId(state)); ++ // We're going to fake the block breaking to match vanilla standards. ++ for (net.minecraft.world.item.ItemStack drop : Block.getDrops(state, level, pos, null)) { // Use base cactus since we don't place a block ++ Block.popResource(level, blockPos, drop); ++ } ++ level.setBlock(pos, state.setValue(CactusBlock.AGE, 0), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE); ++ return; ++ } ++ // Pluto end - Check if the cactus can even survive being placed + org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, blockPos, this.defaultBlockState()); // CraftBukkit + BlockState blockState = state.setValue(AGE, Integer.valueOf(0)); + level.setBlock(pos, blockState, 4); + level.neighborChanged(blockState, blockPos, this, null, false); + } else if (modifier == 100 || random.nextFloat() < (modifier / (100.0f * 16))) { // Spigot - SPIGOT-7159: Better modifier resolution ++ // Pluto start - Check if the cactus can even survive being placed ++ if (org.dreeam.leaf.config.modules.opt.CheckSurvivalBeforeGrowth.cactusCheckSurvivalBeforeGrowth) { ++ level.setBlock(pos, state.setValue(CactusBlock.AGE, ageValue + 1), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE); ++ return; ++ } ++ // Pluto end - Check if the cactus can even survive being placed + level.setBlock(pos, state.setValue(AGE, Integer.valueOf(ageValue + 1)), 4); + } + } +@@ -102,6 +124,12 @@ public class CactusBlock extends Block implements BonemealableBlock { // Purpur + + @Override + protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) { ++ // Pluto start - Check if the cactus can even survive being placed ++ return canSurvive(level, pos); ++ } ++ ++ protected boolean canSurvive(LevelReader level, BlockPos pos) { ++ // Pluto end - Check if the cactus can even survive being placed + for (Direction direction : Direction.Plane.HORIZONTAL) { + BlockState blockState = level.getBlockState(pos.relative(direction)); + if ((level.getWorldBorder().world.purpurConfig.cactusBreaksFromSolidNeighbors && blockState.isSolid()) || level.getFluidState(pos.relative(direction)).is(FluidTags.LAVA)) { // Purpur - Cactus breaks from solid neighbors config diff --git a/leaf-archived-patches/unapplied/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/leaf-archived-patches/unapplied/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java index c17e3484..6a307db7 100644 --- a/leaf-archived-patches/unapplied/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java +++ b/leaf-archived-patches/unapplied/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java @@ -22,6 +22,13 @@ public class ProtocolSupport extends ConfigModules { public static boolean syncmaticaQuota = false; public static int syncmaticaQuotaLimit = 40000000; + public static boolean doABarrelRollProtocol = false; + public static boolean doABarrelRollAllowThrusting = false; + public static boolean doABarrelRollForceEnabled = false; + public static boolean doABarrelRollForceInstalled = false; + public static int doABarrelRollInstalledTimeout = 40; + public static DoABarrelRollPackets.KineticDamage doABarrelRollKineticDamage = DoABarrelRollPackets.KineticDamage.VANILLA; + @Override public void onLoaded() { jadeProtocol = config.getBoolean(getBasePath() + ".jade-protocol", jadeProtocol); @@ -38,5 +45,26 @@ public class ProtocolSupport extends ConfigModules { if (syncmaticaProtocol) { org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol.init(); } + + doABarrelRollProtocol = config.getBoolean(getBasePath() + ".do-a-barrel-roll-protocol", doABarrelRollProtocol); + doABarrelRollAllowThrusting = config.getBoolean(getBasePath() + ".do-a-barrel-roll-allow-thrusting", doABarrelRollAllowThrusting); + doABarrelRollForceEnabled = config.getBoolean(getBasePath() + ".do-a-barrel-roll-force-enabled", doABarrelRollForceEnabled); + doABarrelRollForceInstalled = config.getBoolean(getBasePath() + ".do-a-barrel-roll-force-installed", doABarrelRollForceInstalled); + doABarrelRollInstalledTimeout = config.getInt(getBasePath() + ".do-a-barrel-roll-installed-timeout", 0); + doABarrelRollKineticDamage = DoABarrelRollPackets.KineticDamage.valueOf(config.getString(getBasePath() + ".do-a-barrel-roll-kinetic-damage", doABarrelRollKineticDamage.name())); + if (doABarrelRollInstalledTimeout <= 0) { + doABarrelRollInstalledTimeout = 40; + } + if (doABarrelRollProtocol) { + DoABarrelRollProtocol.init( + doABarrelRollAllowThrusting, + doABarrelRollForceEnabled, + doABarrelRollForceInstalled, + doABarrelRollInstalledTimeout, + doABarrelRollKineticDamage + ); + } else { + DoABarrelRollProtocol.deinit(); + } } } 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 827e49a5..42ea225e 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..50137b67b578b0f9f34bb11a6d572df99ee9fa37 100644 +index 9dce1d22c7de3a3dd0e0e8f117cfbb54d1b15042..405b62c082017024abae7ccc1db5f74caab1eabf 100644 --- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -70,6 +70,11 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, @@ -29,7 +29,7 @@ index 9dce1d22c7de3a3dd0e0e8f117cfbb54d1b15042..50137b67b578b0f9f34bb11a6d572df9 - .hasJoinedServer(string1, string, this.getAddress()); + // Leaf start - Cache player profileResult + ProfileResult profileResult; -+ if (org.dreeam.leaf.config.modules.misc.Cache.cachePlayerProfileResult) { ++ if (false) { // TODO + profileResult = playerProfileResultCache.getIfPresent(string1); + + if (profileResult == null) { diff --git a/leaf-server/minecraft-patches/features/0141-Configurable-unknown-command-message.patch b/leaf-server/minecraft-patches/features/0141-Configurable-unknown-command-message.patch index e1884f03..8db95490 100644 --- a/leaf-server/minecraft-patches/features/0141-Configurable-unknown-command-message.patch +++ b/leaf-server/minecraft-patches/features/0141-Configurable-unknown-command-message.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Configurable unknown command message diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java -index aba02a7e3139030050c3c61aabf7708c6c088a68..a6d5ad7690f3acc7e80770e24c41d7dbf2b71861 100644 +index aba02a7e3139030050c3c61aabf7708c6c088a68..4bc1aa7a85596fa1bb275e88834b25f4fe2c6ea0 100644 --- a/net/minecraft/commands/Commands.java +++ b/net/minecraft/commands/Commands.java @@ -403,31 +403,8 @@ public class Commands { @@ -41,59 +41,65 @@ index aba02a7e3139030050c3c61aabf7708c6c088a68..a6d5ad7690f3acc7e80770e24c41d7db org.bukkit.Bukkit.getServer().getPluginManager().callEvent(event); if (event.message() != null) { source.sendFailure(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false); -@@ -677,6 +654,86 @@ public class Commands { +@@ -677,6 +654,92 @@ public class Commands { }; } + // Leaf start - Configurable unknown command message -+ private static net.kyori.adventure.text.TextComponent getUnknownCommandMessage( ++ private static net.kyori.adventure.text.Component getUnknownCommandMessage( + net.kyori.adventure.text.TextComponent.Builder builder, CommandSyntaxException commandSyntaxException, String label + ) { + String rawMessage = org.dreeam.leaf.config.modules.misc.UnknownCommandMessage.unknownCommandMessage; + -+ if (!"default".equals(rawMessage)) { -+ final String input = commandSyntaxException.getInput(); -+ final int cursor = commandSyntaxException.getCursor(); -+ -+ if (rawMessage.contains("") && input != null && cursor >= 0) { -+ final int min = Math.min(input.length(), cursor); -+ final net.kyori.adventure.text.TextComponent.Builder detail = net.kyori.adventure.text.Component.text(); -+ final net.kyori.adventure.text.Component context = net.kyori.adventure.text.Component.translatable("command.context.here") -+ .color(net.kyori.adventure.text.format.NamedTextColor.RED) -+ .decorate(net.kyori.adventure.text.format.TextDecoration.ITALIC); -+ final net.kyori.adventure.text.event.ClickEvent event = net.kyori.adventure.text.event.ClickEvent.suggestCommand("/" + label); -+ -+ detail.color(net.kyori.adventure.text.format.NamedTextColor.GRAY); -+ -+ if (min > 10) { -+ detail.append(net.kyori.adventure.text.Component.text("...")); -+ } -+ -+ detail.append(net.kyori.adventure.text.Component.text(input.substring(Math.max(0, min - 10), min))); -+ if (min < input.length()) { -+ net.kyori.adventure.text.Component commandInput = net.kyori.adventure.text.Component.text(input.substring(min)) -+ .color(net.kyori.adventure.text.format.NamedTextColor.RED) -+ .decorate(net.kyori.adventure.text.format.TextDecoration.UNDERLINED); -+ -+ detail.append(commandInput); -+ } -+ -+ detail.append(context); -+ detail.clickEvent(event); -+ -+ builder.append(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(rawMessage, net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("detail", detail.build()))); -+ } else { -+ rawMessage = rawMessage.replace("", ""); -+ builder.append(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(rawMessage)); -+ } -+ -+ return builder.build(); ++ if ("default".equals(rawMessage)) { ++ return getVanillaUnknownCommandMessage(builder, commandSyntaxException, label); + } + -+ return getVanillaUnknownCommandMessage(builder, commandSyntaxException, label); ++ net.kyori.adventure.text.Component messageComponent = null; ++ net.kyori.adventure.text.Component detailComponent = null; ++ ++ if (rawMessage.contains("")) { ++ messageComponent = io.papermc.paper.command.brigadier.MessageComponentSerializer.message().deserialize(commandSyntaxException.getRawMessage()); ++ } ++ ++ final String input = commandSyntaxException.getInput(); ++ final int cursor = commandSyntaxException.getCursor(); ++ ++ if (rawMessage.contains("") && input != null && cursor >= 0) { ++ final int min = Math.min(input.length(), cursor); ++ final net.kyori.adventure.text.TextComponent.Builder detail = net.kyori.adventure.text.Component.text(); ++ final net.kyori.adventure.text.Component context = net.kyori.adventure.text.Component.translatable("command.context.here") ++ .color(net.kyori.adventure.text.format.NamedTextColor.RED) ++ .decorate(net.kyori.adventure.text.format.TextDecoration.ITALIC); ++ final net.kyori.adventure.text.event.ClickEvent event = net.kyori.adventure.text.event.ClickEvent.suggestCommand("/" + label); ++ ++ detail.color(net.kyori.adventure.text.format.NamedTextColor.GRAY); ++ ++ if (min > 10) { ++ detail.append(net.kyori.adventure.text.Component.text("...")); ++ } ++ ++ detail.append(net.kyori.adventure.text.Component.text(input.substring(Math.max(0, min - 10), min))); ++ if (min < input.length()) { ++ net.kyori.adventure.text.Component commandInput = net.kyori.adventure.text.Component.text(input.substring(min)) ++ .color(net.kyori.adventure.text.format.NamedTextColor.RED) ++ .decorate(net.kyori.adventure.text.format.TextDecoration.UNDERLINED); ++ ++ detail.append(commandInput); ++ } ++ ++ detail.append(context); ++ detail.clickEvent(event); ++ ++ detailComponent = detail.build(); ++ } ++ ++ return builder.append(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(rawMessage)).build() ++ .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("").replacement(messageComponent).build()) ++ .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("").replacement(detailComponent).build()); + } + -+ private static net.kyori.adventure.text.TextComponent getVanillaUnknownCommandMessage( ++ private static net.kyori.adventure.text.Component getVanillaUnknownCommandMessage( + net.kyori.adventure.text.TextComponent.Builder builder, CommandSyntaxException var7, String label + ) { + builder.color(net.kyori.adventure.text.format.NamedTextColor.RED).append(io.papermc.paper.command.brigadier.MessageComponentSerializer.message().deserialize(var7.getRawMessage())); diff --git a/leaf-server/minecraft-patches/features/0170-Multithreaded-Tracker.patch b/leaf-server/minecraft-patches/features/0170-Multithreaded-Tracker.patch index 18693aa4..8da412f2 100644 --- a/leaf-server/minecraft-patches/features/0170-Multithreaded-Tracker.patch +++ b/leaf-server/minecraft-patches/features/0170-Multithreaded-Tracker.patch @@ -37,7 +37,7 @@ index 02a9ef1694c796584c29430d27f0a09047368835..32608df3da169159c070f37cb55407f4 private static final byte CHUNK_TICKET_STAGE_NONE = 0; private static final byte CHUNK_TICKET_STAGE_LOADING = 1; diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java -index c60b9e4076450de2157c1a3cf4f98cc2c19e4e6a..560a857f3b61679bbf2ee93ac6da393052a1f320 100644 +index c60b9e4076450de2157c1a3cf4f98cc2c19e4e6a..41da66fd77924a9a4bd9cc12f517e2e693645dfe 100644 --- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java @@ -255,6 +255,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -62,7 +62,7 @@ index c60b9e4076450de2157c1a3cf4f98cc2c19e4e6a..560a857f3b61679bbf2ee93ac6da3930 protected void tick() { + // Leaf start - Multithreaded tracker + if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { -+ final ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel level = this.level; ++ final ServerLevel level = this.level; + org.dreeam.leaf.async.tracker.MultithreadedTracker.tick(level); + return; + } @@ -70,89 +70,194 @@ index c60b9e4076450de2157c1a3cf4f98cc2c19e4e6a..560a857f3b61679bbf2ee93ac6da3930 // Paper start - optimise entity tracker if (true) { this.newTrackerTick(); -@@ -1135,7 +1151,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1135,7 +1151,18 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider final Entity entity; private final int range; SectionPos lastSectionPos; - public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl -+ public final Set seenBy = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled ? com.google.common.collect.Sets.newConcurrentHashSet() : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl // Leaf - petal - Multithreaded tracker ++ // Leaf start - Multithreaded tracker ++ public static final ServerPlayerConnection[] EMPTY_OBJECT_ARRAY = new ServerPlayerConnection[0]; ++ public final Object sync = new Object(); ++ public final Set seenBy = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled ? it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>()) : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl ++ private volatile ServerPlayerConnection[] seenByArray = EMPTY_OBJECT_ARRAY; ++ public ServerPlayerConnection[] seenBy() { ++ return seenByArray; ++ } ++ public void seenByUpdated() { ++ this.seenByArray = this.seenBy.toArray(EMPTY_OBJECT_ARRAY); ++ } ++ // Leaf end - Multithreaded tracker // Paper start - optimise entity tracker private long lastChunkUpdate = -1L; -@@ -1162,7 +1178,39 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1162,27 +1189,95 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.lastTrackedChunk = chunk; final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); -+ final int playersLen = players.size(); // Ensure length won't change in the future tasks -+ -+ // Leaf start - Multithreaded tracker -+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled && org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) { -+ final boolean isServerPlayer = this.entity instanceof ServerPlayer; -+ final boolean isRealPlayer = isServerPlayer && ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer) this.entity).moonrise$isRealPlayer(); -+ Runnable updatePlayerTasks = () -> { -+ for (int i = 0; i < playersLen; ++i) { -+ final ServerPlayer player = playersRaw[i]; -+ this.updatePlayer(player); -+ } - -+ if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { -+ // need to purge any players possible not in the chunk list -+ for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { -+ final ServerPlayer player = conn.getPlayer(); -+ if (!players.contains(player)) { -+ this.removePlayer(player); -+ } -+ } -+ } -+ }; -+ -+ // Only update asynchronously for real player, and sync update for fake players -+ // This can fix compatibility issue with NPC plugins using real entity type, like Citizens -+ // To prevent visible issue with player type NPCs -+ // btw, still recommend to use packet based NPC plugins, like ZNPC Plus, Adyeshach, Fancy NPC, etc. -+ if (isRealPlayer || !isServerPlayer) { -+ org.dreeam.leaf.async.tracker.MultithreadedTracker.getTrackerExecutor().execute(updatePlayerTasks); -+ } else { -+ updatePlayerTasks.run(); -+ } -+ } else { - for (int i = 0, len = players.size(); i < len; ++i) { +- +- for (int i = 0, len = players.size(); i < len; ++i) { ++ final int playersLength = Math.min(playersRaw.length, players.size()); // Leaf - Multithreaded tracker ++ for (int i = 0; i < playersLength; ++i) { // Leaf - Multithreaded tracker final ServerPlayer player = playersRaw[i]; this.updatePlayer(player); -@@ -1177,6 +1225,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { + // need to purge any players possible not in the chunk list +- for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { ++ // Leaf start - Multithreaded tracker ++ boolean removed = false; ++ for (final ServerPlayerConnection conn : this.seenBy()) { + final ServerPlayer player = conn.getPlayer(); + if (!players.contains(player)) { +- this.removePlayer(player); ++ removed |= this.removePlayerMulti(player); } } ++ if (removed) { ++ this.seenByUpdated(); ++ } } + } ++ // Leaf end - Multithreaded tracker ++ ++ // Leaf start - Multithreaded tracker ++ public final @Nullable Runnable leafTickCompact(final ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk chunk) { ++ if (chunk == null) { ++ this.moonrise$clearPlayers(); ++ return null; + } -+ // Leaf end - Multithreaded tracker ++ ++ final ca.spottedleaf.moonrise.common.list.ReferenceList players = chunk.getPlayers(ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.VIEW_DISTANCE); ++ ++ if (players == null) { ++ this.moonrise$clearPlayers(); ++ return null; ++ } ++ ++ final long lastChunkUpdate = this.lastChunkUpdate; ++ final long currChunkUpdate = chunk.getUpdateCount(); ++ final ca.spottedleaf.moonrise.common.misc.NearbyPlayers.TrackedChunk lastTrackedChunk = this.lastTrackedChunk; ++ this.lastChunkUpdate = currChunkUpdate; ++ this.lastTrackedChunk = chunk; ++ ++ final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); ++ final int playersLen = players.size(); // Ensure length won't change in the future tasks ++ ++ if (!org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled || !org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) { ++ throw new IllegalStateException(); ++ } ++ final boolean isServerPlayer = this.entity instanceof ServerPlayer; ++ final boolean isRealPlayer = isServerPlayer && ((ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer) this.entity).moonrise$isRealPlayer(); ++ Runnable updatePlayerTasks = () -> { ++ for (int i = 0; i < playersLen; ++i) { ++ final ServerPlayer player = playersRaw[i]; ++ this.updatePlayer(player); ++ } ++ ++ if (lastChunkUpdate != currChunkUpdate || lastTrackedChunk != chunk) { ++ // need to purge any players possible not in the chunk list ++ boolean removed = false; ++ for (final ServerPlayerConnection conn : this.seenBy()) { ++ final ServerPlayer player = conn.getPlayer(); ++ if (!players.contains(player)) { ++ removed |= this.removePlayerMulti(player); ++ } ++ } ++ if (removed) { ++ this.seenByUpdated(); ++ } ++ } ++ }; ++ ++ // Only update asynchronously for real player, and sync update for fake players ++ // This can fix compatibility issue with NPC plugins using real entity type, like Citizens ++ // To prevent visible issue with player type NPCs ++ // btw, still recommend to use packet based NPC plugins, like ZNPC Plus, Adyeshach, Fancy NPC, etc. ++ if (isRealPlayer || !isServerPlayer) { ++ return updatePlayerTasks; ++ } else { ++ updatePlayerTasks.run(); ++ return null; ++ } ++ } ++ // Leaf end - Multithreaded tracker + + @Override + public final void moonrise$removeNonTickThreadPlayers() { + boolean foundToRemove = false; +- for (final ServerPlayerConnection conn : this.seenBy) { ++ for (final ServerPlayerConnection conn : this.seenBy()) { // Leaf - Multithreaded tracker + if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(conn.getPlayer())) { + foundToRemove = true; + break; +@@ -1193,12 +1288,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return; + } + +- for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { ++ for (final ServerPlayerConnection conn : this.seenBy()) { // Leaf - Multithreaded tracker + ServerPlayer player = conn.getPlayer(); + if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(player)) { +- this.removePlayer(player); ++ this.removePlayerMulti(player); // Leaf - Multithreaded tracker + } + } ++ this.seenByUpdated(); // Leaf - Multithreaded tracker } @Override -@@ -1238,7 +1288,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1208,10 +1304,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + if (this.seenBy.isEmpty()) { + return; + } +- for (final ServerPlayerConnection conn : new java.util.ArrayList<>(this.seenBy)) { ++ for (final ServerPlayerConnection conn : this.seenBy()) { // Leaf - Multithreaded tracker + ServerPlayer player = conn.getPlayer(); +- this.removePlayer(player); ++ this.removePlayerMulti(player); // Leaf - Multithreaded tracker + } ++ this.seenByUpdated(); // Leaf - Multithreaded tracker + } + + @Override +@@ -1238,7 +1335,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void broadcast(Packet packet) { - for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { -+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {// Leaf - petal - Multithreaded tracker ++ for (ServerPlayerConnection serverPlayerConnection : this.seenBy()) { // Leaf - petal - Multithreaded tracker serverPlayerConnection.send(packet); } } -@@ -1259,21 +1309,22 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1259,21 +1356,34 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void broadcastRemoved() { - for (ServerPlayerConnection serverPlayerConnection : this.seenBy) { -+ for (ServerPlayerConnection serverPlayerConnection : this.seenBy.toArray(new ServerPlayerConnection[0])) {// Leaf - petal - Multithreaded tracker ++ for (ServerPlayerConnection serverPlayerConnection : this.seenBy()) { // Leaf - petal - Multithreaded tracker this.serverEntity.removePairing(serverPlayerConnection.getPlayer()); } } ++ // Leaf start - Multithreaded tracker ++ public boolean removePlayerMulti(ServerPlayer player) { ++ if (this.seenBy.remove(player.connection)) { ++ this.serverEntity.removePairing(player); ++ return true; ++ } else { ++ return false; ++ } ++ } ++ // Leaf end - Multithreaded tracker ++ public void removePlayer(ServerPlayer player) { - org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot + //org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot // Leaf - petal - Multithreaded tracker - We can remove async too if (this.seenBy.remove(player.connection)) { this.serverEntity.removePairing(player); } ++ this.seenByUpdated(); // Leaf - Multithreaded tracker } public void updatePlayer(ServerPlayer player) { @@ -163,6 +268,22 @@ index c60b9e4076450de2157c1a3cf4f98cc2c19e4e6a..560a857f3b61679bbf2ee93ac6da3930 // Paper start - remove allocation of Vec3D here // Vec3 vec3 = player.position().subtract(this.entity.position()); double vec3_dx = player.getX() - this.entity.getX(); +@@ -1301,6 +1411,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // CraftBukkit end + if (flag) { + if (this.seenBy.add(player.connection)) { ++ this.seenByUpdated(); // Leaf - Multithreaded tracker + // Paper start - entity tracking events + if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) { + this.serverEntity.addPairing(player); +@@ -1309,6 +1420,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.serverEntity.onPlayerAdd(); // Paper - fix desync when a player is added to the tracker + } + } else if (this.seenBy.remove(player.connection)) { ++ this.seenByUpdated(); // Leaf - Multithreaded tracker + this.serverEntity.removePairing(player); + } + } diff --git a/net/minecraft/server/level/ServerBossEvent.java b/net/minecraft/server/level/ServerBossEvent.java index f106373ef3ac4a8685c2939c9e8361688a285913..51ae390c68e7a3aa193329cc3bc47ca675930ff2 100644 --- a/net/minecraft/server/level/ServerBossEvent.java @@ -177,31 +298,39 @@ 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..87b032ad2ba3e3e0a2e5cfcf185533102247a946 100644 +index 1a9601aee097b6c10cf2ae1c52fddf45da85f60f..867936866d952c559b6ffa49fdf78acd70a9bab9 100644 --- a/net/minecraft/server/level/ServerEntity.java +++ b/net/minecraft/server/level/ServerEntity.java -@@ -460,15 +460,18 @@ public class ServerEntity { - if (this.entity instanceof LivingEntity) { - Set attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync(); - if (!attributesToSync.isEmpty()) { -+ // Leaf start - petal - Multithreaded tracker - send in main thread -+ final Set copy = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(attributesToSync); - // CraftBukkit start - Send scaled max health - if (this.entity instanceof ServerPlayer serverPlayer) { -- serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false); -+ serverPlayer.getBukkitEntity().injectScaledMaxHealth(copy, false); - } - // CraftBukkit end -- this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); -+ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), copy)); -+ // Leaf end - petal - Multithreaded tracker - send in main thread - } +@@ -75,6 +75,7 @@ public class ServerEntity { + @Nullable + private List> trackedDataValues; + private final Set trackedPlayers; // Paper ++ public boolean wantSendDirtyEntityData = false; // Leaf - Multithreaded tracker -- attributesToSync.clear(); -+ ((LivingEntity)this.entity).getAttributes().getAttributesToSync().clear(); // Leaf - Multithreaded tracker - } + public ServerEntity( + ServerLevel level, +@@ -146,7 +147,7 @@ public class ServerEntity { + MapId mapId = itemFrame.cachedMapId; // Paper - Perf: Cache map ids on item frames + MapItemSavedData savedData = MapItem.getSavedData(mapId, this.level); + if (savedData != null) { +- for (final net.minecraft.server.network.ServerPlayerConnection connection : this.trackedPlayers) { // Paper ++ for (final net.minecraft.server.network.ServerPlayerConnection connection : this.trackedPlayers.toArray(ChunkMap.TrackedEntity.EMPTY_OBJECT_ARRAY)) { // Paper // Leaf - Multithreaded tracker + final ServerPlayer serverPlayer = connection.getPlayer(); // Paper + savedData.tickCarriedBy(serverPlayer, item); + Packet updatePacket = savedData.getUpdatePacket(mapId, serverPlayer); +@@ -450,6 +451,12 @@ public class ServerEntity { } + public void sendDirtyEntityData() { ++ // Leaf start - Multithreaded tracker ++ if (Thread.currentThread() instanceof org.dreeam.leaf.async.tracker.MultithreadedTracker.MultithreadedTrackerThread) { ++ wantSendDirtyEntityData = true; ++ return; ++ } ++ // Leaf end - Multithreaded tracker + SynchedEntityData entityData = this.entity.getEntityData(); + List> list = entityData.packDirty(); + if (list != null) { 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 @@ -237,98 +366,55 @@ index 729f595491c7a4edf24dff2e876dfb69ade87a17..a3069b29a1b78012314747d705e27c16 // Paper start - Prevent teleporting dead entities if (this.player.isRemoved()) { LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); -diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -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 { - private final Map> modifiersByOperation = Maps.newEnumMap( - AttributeModifier.Operation.class - ); -- private final Map modifierById = new Object2ObjectArrayMap<>(); -- 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 ? 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 { - } +diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java +index 36e3937c9e09852937c94c268c877a15337835c5..8aedc3ca463745fe32cac977208b23dc0b8e73b6 100644 +--- a/net/minecraft/world/entity/item/PrimedTnt.java ++++ b/net/minecraft/world/entity/item/PrimedTnt.java +@@ -145,12 +145,14 @@ public class PrimedTnt extends Entity implements TraceableEntity { + net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket velocityPacket = new net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket(this); + net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket positionPacket = net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket.teleport(this.getId(), net.minecraft.world.entity.PositionMoveRotation.of(this), java.util.Set.of(), this.onGround); - 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; +- ete.seenBy.stream() +- .filter(viewer -> (viewer.getPlayer().getX() - this.getX()) * (viewer.getPlayer().getY() - this.getY()) * (viewer.getPlayer().getZ() - this.getZ()) < 16 * 16) +- .forEach(viewer -> { ++ // Leaf start - Multithreaded tracker ++ for (var viewer : ete.seenBy()) { ++ if ((viewer.getPlayer().getX() - this.getX()) * (viewer.getPlayer().getY() - this.getY()) * (viewer.getPlayer().getZ() - this.getZ()) < 16 * 16) { + viewer.send(velocityPacket); + viewer.send(positionPacket); +- }); ++ } + } -+ 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..8a299c81799b3f0c353eecce56afd14b9150df5f 100644 ---- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java -+++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java -@@ -14,11 +14,11 @@ import net.minecraft.nbt.ListTag; - import net.minecraft.resources.ResourceLocation; ++ // Leaf end - Multithreaded tracker + } + } + // Paper end - Option to prevent TNT from moving in water +diff --git a/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java b/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java +index 325ec57df2885f5e81b8a6b61e3a9fed9484b30f..abc5c097861d0decf49d0d3970ab48f1cf8b1cf1 100644 +--- a/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java ++++ b/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java +@@ -35,13 +35,20 @@ public class NewMinecartBehavior extends MinecartBehavior { + private int cachedLerpDelay; + private float cachedPartialTick; + private int lerpDelay = 0; +- public final List lerpSteps = new LinkedList<>(); ++ public final List lerpSteps; // Leaf - Multithreaded tracker + public final List currentLerpSteps = new LinkedList<>(); + public double currentLerpStepsTotalWeight = 0.0; + public NewMinecartBehavior.MinecartStep oldLerp = NewMinecartBehavior.MinecartStep.ZERO; - public class AttributeMap { -- // 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); -- // 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 + public NewMinecartBehavior(AbstractMinecart minecart) { + super(minecart); + // 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)); ++ lerpSteps = it.unimi.dsi.fastutil.objects.ObjectLists.synchronize(new it.unimi.dsi.fastutil.objects.ObjectArrayList<>()); + } 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); ++ lerpSteps = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); + } + // Leaf end - Multithreaded tracker } - private void onAttributeModified(AttributeInstance instance) { + @Override 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/0175-Smart-sort-entities-in-NearestLivingEntitySensor.patch b/leaf-server/minecraft-patches/features/0175-Smart-sort-entities-in-NearestLivingEntitySensor.patch index 2bb35dff..96454dc8 100644 --- a/leaf-server/minecraft-patches/features/0175-Smart-sort-entities-in-NearestLivingEntitySensor.patch +++ b/leaf-server/minecraft-patches/features/0175-Smart-sort-entities-in-NearestLivingEntitySensor.patch @@ -12,116 +12,40 @@ In non-strict test, this can give ~60-110% improvement (524ms on Paper, 204ms on under 625 villagers situation. diff --git a/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java b/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java -index b0c5e41fefc7c9adf1a61bd5b52861736657d37e..83af90c7a2e425b775abd7907895d211ced07955 100644 +index b0c5e41fefc7c9adf1a61bd5b52861736657d37e..ef87b95d53e5ea2555778e6020ea07a11c474961 100644 --- a/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java +++ b/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java -@@ -13,18 +13,102 @@ import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities; +@@ -13,17 +13,27 @@ import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities; import net.minecraft.world.phys.AABB; public class NearestLivingEntitySensor extends Sensor { + -+ // Leaf start - Optimized entity sorting with buffer reuse -+ private static final int SMALL_ARRAY_THRESHOLD = 2; -+ private LivingEntity[] entityBuffer = new LivingEntity[0]; -+ private long[] bitsBuffer = new long[0]; ++ // Leaf start - Smart sort entities in NearestLivingEntitySensor ++ private final org.dreeam.leaf.util.FastBitRadixSort sorter; ++ public NearestLivingEntitySensor() { ++ this.sorter = new org.dreeam.leaf.util.FastBitRadixSort(); ++ } ++ // Leaf end - Smart sort entities in NearestLivingEntitySensor + @Override protected void doTick(ServerLevel level, T entity) { -- double attributeValue = entity.getAttributeValue(Attributes.FOLLOW_RANGE); -- AABB aabb = entity.getBoundingBox().inflate(attributeValue, attributeValue, attributeValue); + double attributeValue = entity.getAttributeValue(Attributes.FOLLOW_RANGE); + AABB aabb = entity.getBoundingBox().inflate(attributeValue, attributeValue, attributeValue); - List entitiesOfClass = level.getEntitiesOfClass( - LivingEntity.class, aabb, matchableEntity -> matchableEntity != entity && matchableEntity.isAlive() -+ double range = entity.getAttributeValue(Attributes.FOLLOW_RANGE); -+ double rangeSqr = range * range; -+ AABB aabb = entity.getBoundingBox().inflate(range, range, range); -+ -+ List entities = level.getEntitiesOfClass( -+ LivingEntity.class, aabb, e -> e != entity && e.isAlive() && entity.distanceToSqr(e) <= rangeSqr - ); +- ); - entitiesOfClass.sort(Comparator.comparingDouble(entity::distanceToSqr)); -+ -+ LivingEntity[] sorted = smartSort(entities, entity); ++ // Leaf start - Smart sort entities in NearestLivingEntitySensor ++ double rangeSqr = attributeValue * attributeValue; ++ List entities = level.getEntitiesOfClass(LivingEntity.class, aabb, e -> e != entity && e.isAlive() && entity.distanceToSqr(e) <= rangeSqr); ++ LivingEntity[] sorted = this.sorter.sort(entities, entity, LivingEntity.class); + List sortedList = java.util.Arrays.asList(sorted); -+ Brain brain = entity.getBrain(); - brain.setMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES, entitiesOfClass); - brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, new NearestVisibleLivingEntities(level, entity, entitiesOfClass)); + brain.setMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES, sortedList); -+ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, -+ new NearestVisibleLivingEntities(level, entity, sortedList)); -+ } -+ -+ private LivingEntity[] smartSort(List entities, T reference) { -+ int size = entities.size(); -+ if (size <= 1) return entities.toArray(new LivingEntity[0]); -+ -+ if (entityBuffer.length < size) { -+ entityBuffer = new LivingEntity[size]; -+ bitsBuffer = new long[size]; -+ } -+ -+ for (int i = 0; i < size; i++) { -+ LivingEntity e = entities.get(i); -+ entityBuffer[i] = e; -+ bitsBuffer[i] = Double.doubleToRawLongBits(reference.distanceToSqr(e)); -+ } -+ -+ fastRadixSort(entityBuffer, bitsBuffer, 0, size - 1, 62); -+ -+ return java.util.Arrays.copyOf(entityBuffer, size); -+ } -+ -+ private void fastRadixSort(LivingEntity[] ents, long[] bits, int low, int high, int bit) { -+ if (bit < 0 || low >= high) return; -+ -+ if (high - low <= SMALL_ARRAY_THRESHOLD) { -+ insertionSort(ents, bits, low, high); -+ return; -+ } -+ -+ int i = low, j = high; -+ final long mask = 1L << bit; -+ -+ while (i <= j) { -+ while (i <= j && (bits[i] & mask) == 0) i++; -+ while (i <= j && (bits[j] & mask) != 0) j--; -+ -+ if (i < j) { -+ swap(ents, bits, i++, j--); -+ } -+ } -+ -+ if (low < j) fastRadixSort(ents, bits, low, j, bit - 1); -+ if (i < high) fastRadixSort(ents, bits, i, high, bit - 1); -+ } -+ -+ private void insertionSort(LivingEntity[] ents, long[] bits, int low, int high) { -+ for (int i = low + 1; i <= high; i++) { -+ int j = i; -+ LivingEntity e = ents[j]; -+ long b = bits[j]; -+ -+ while (j > low && bits[j - 1] > b) { -+ ents[j] = ents[j - 1]; -+ bits[j] = bits[j - 1]; -+ j--; -+ } -+ -+ ents[j] = e; -+ bits[j] = b; -+ } -+ } -+ -+ private void swap(LivingEntity[] ents, long[] bits, int a, int b) { -+ LivingEntity te = ents[a]; -+ ents[a] = ents[b]; -+ ents[b] = te; -+ -+ long tb = bits[a]; -+ bits[a] = bits[b]; -+ bits[b] = tb; ++ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, new NearestVisibleLivingEntities(level, entity, sortedList)); ++ // Leaf end - Smart sort entities in NearestLivingEntitySensor } -+ // Leaf end - Optimized entity sorting with buffer reuse @Override - public Set> requires() { diff --git a/leaf-server/minecraft-patches/features/0216-Async-chunk-send.patch b/leaf-server/minecraft-patches/features/0216-Async-chunk-send.patch new file mode 100644 index 00000000..e648120f --- /dev/null +++ b/leaf-server/minecraft-patches/features/0216-Async-chunk-send.patch @@ -0,0 +1,173 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sun, 2 Mar 2025 21:23:20 +0100 +Subject: [PATCH] Async chunk send + + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +index 32608df3da169159c070f37cb55407f4f6187744..3a78e7512772fd3f7cf8f221e3a72474def14bea 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +@@ -436,7 +436,15 @@ public final class RegionizedPlayerChunkLoader { + // Note: drop isAlive() check so that chunks properly unload client-side when the player dies + ((ChunkSystemChunkHolder)((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager + .getChunkHolder(chunkX, chunkZ).vanillaChunkHolder).moonrise$removeReceivedChunk(this.player); +- this.player.connection.send(new ClientboundForgetLevelChunkPacket(new ChunkPos(chunkX, chunkZ))); ++ // Leaf start - Async chunk send ++ if (org.dreeam.leaf.config.modules.async.AsyncChunkSend.enabled) { ++ org.dreeam.leaf.async.chunk.AsyncChunkSend.POOL.execute( ++ () -> this.player.connection.send(new ClientboundForgetLevelChunkPacket(new ChunkPos(chunkX, chunkZ))) ++ ); ++ } else { ++ this.player.connection.send(new ClientboundForgetLevelChunkPacket(new ChunkPos(chunkX, chunkZ))); ++ } ++ // Leaf end - Async chunk send + // Paper start - PlayerChunkUnloadEvent + if (io.papermc.paper.event.packet.PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0) { + new io.papermc.paper.event.packet.PlayerChunkUnloadEvent(player.getBukkitEntity().getWorld().getChunkAt(new ChunkPos(chunkX, chunkZ).longKey), player.getBukkitEntity()).callEvent(); +diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java +index 526c117e0d53ad527eb610c79cdc46ec16b18c0c..9151e580b4840fddab04e487d723130a5a769a1a 100644 +--- a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java ++++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java +@@ -75,6 +75,45 @@ public class ClientboundLevelChunkPacketData { + } + } + ++ // Leaf start - Async chunk send ++ public ClientboundLevelChunkPacketData(LevelChunk levelChunk, io.papermc.paper.antixray.ChunkPacketInfo chunkPacketInfo, BlockEntity[] blockEntities, Map heightmaps) { ++ this.heightmaps = heightmaps; ++ ++ if (Thread.currentThread() instanceof org.dreeam.leaf.async.chunk.AsyncChunkSendThread) { ++ var buffer = new io.netty.buffer.UnpooledByteBufAllocator(false).buffer(calculateChunkSize(levelChunk)); ++ extractChunkData(new FriendlyByteBuf(buffer), levelChunk, chunkPacketInfo); ++ var array = it.unimi.dsi.fastutil.bytes.ByteArrays.trim(buffer.array(), buffer.writerIndex()); ++ if (chunkPacketInfo != null) { ++ chunkPacketInfo.setBuffer(array); ++ } ++ this.buffer = array; ++ } else { ++ this.buffer = new byte[calculateChunkSize(levelChunk)]; ++ // Paper start - Anti-Xray - Add chunk packet info ++ if (chunkPacketInfo != null) { ++ chunkPacketInfo.setBuffer(this.buffer); ++ } ++ extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), levelChunk, chunkPacketInfo); ++ } ++ ++ this.blockEntitiesData = Lists.newArrayList(); ++ int totalTileEntities = 0; // Paper - Handle oversized block entities in chunks ++ ++ for (BlockEntity blockEntity : blockEntities) { ++ // Paper start - Handle oversized block entities in chunks ++ if (++totalTileEntities > BLOCK_ENTITY_LIMIT) { ++ net.minecraft.network.protocol.Packet packet = blockEntity.getUpdatePacket(); ++ if (packet != null) { ++ this.extraPackets.add(packet); ++ continue; ++ } ++ } ++ // Paper end - Handle oversized block entities in chunks ++ this.blockEntitiesData.add(ClientboundLevelChunkPacketData.BlockEntityInfo.create(blockEntity)); ++ } ++ } ++ // Leaf end - Async chunk send ++ + public ClientboundLevelChunkPacketData(RegistryFriendlyByteBuf buffer, int x, int z) { + this.heightmaps = HEIGHTMAPS_STREAM_CODEC.decode(buffer); + int varInt = buffer.readVarInt(); +diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java +index 8578d1f78ddd1bb75f3230f04bfaa35af9f5f822..4f54c4c8e49c1e0352ab2c5c23277b4103504c55 100644 +--- a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java +@@ -44,6 +44,17 @@ public class ClientboundLevelChunkWithLightPacket implements Packet heightmaps) { ++ ChunkPos pos = chunk.getPos(); ++ this.x = pos.x; ++ this.z = pos.z; ++ io.papermc.paper.antixray.ChunkPacketInfo chunkPacketInfo = modifyBlocks ? chunk.getLevel().chunkPacketBlockController.getChunkPacketInfo(this, chunk) : null; // Paper - Ant-Xray ++ this.chunkData = new ClientboundLevelChunkPacketData(chunk, chunkPacketInfo, blockEntities, heightmaps); // Paper - Anti-Xray ++ this.lightData = new ClientboundLightUpdatePacketData(pos, lightEngine, skyLight, blockLight); ++ chunk.getLevel().chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks ++ } ++ // Leaf end - Async chunk send + + private ClientboundLevelChunkWithLightPacket(RegistryFriendlyByteBuf buffer) { + this.x = buffer.readInt(); +diff --git a/net/minecraft/server/network/PlayerChunkSender.java b/net/minecraft/server/network/PlayerChunkSender.java +index 14878690a88fd4de3e2c127086607e6c819c636c..69581890ab34af20f9c608678f378ec9fe3ec775 100644 +--- a/net/minecraft/server/network/PlayerChunkSender.java ++++ b/net/minecraft/server/network/PlayerChunkSender.java +@@ -64,13 +64,29 @@ public class PlayerChunkSender { + if (!list.isEmpty()) { + ServerGamePacketListenerImpl serverGamePacketListenerImpl = player.connection; + this.unacknowledgedBatches++; +- serverGamePacketListenerImpl.send(ClientboundChunkBatchStartPacket.INSTANCE); ++ // Leaf start - Async chunk send ++ if (org.dreeam.leaf.config.modules.async.AsyncChunkSend.enabled) { ++ org.dreeam.leaf.async.chunk.AsyncChunkSend.POOL.execute( ++ () -> serverGamePacketListenerImpl.send(ClientboundChunkBatchStartPacket.INSTANCE) ++ ); ++ } else { ++ serverGamePacketListenerImpl.send(ClientboundChunkBatchStartPacket.INSTANCE); ++ } ++ // Leaf end - Async chunk send + + for (LevelChunk levelChunk : list) { + sendChunk(serverGamePacketListenerImpl, serverLevel, levelChunk); + } + +- serverGamePacketListenerImpl.send(new ClientboundChunkBatchFinishedPacket(list.size())); ++ // Leaf start - Async chunk send ++ if (org.dreeam.leaf.config.modules.async.AsyncChunkSend.enabled) { ++ org.dreeam.leaf.async.chunk.AsyncChunkSend.POOL.execute( ++ () -> serverGamePacketListenerImpl.send(new ClientboundChunkBatchFinishedPacket(list.size())) ++ ); ++ } else { ++ serverGamePacketListenerImpl.send(new ClientboundChunkBatchFinishedPacket(list.size())); ++ } ++ // Leaf end - Async chunk send + this.batchQuota = this.batchQuota - list.size(); + } + } +@@ -81,7 +97,23 @@ public class PlayerChunkSender { + // Paper start - Anti-Xray + public static void sendChunk(ServerGamePacketListenerImpl packetListener, ServerLevel level, LevelChunk chunk) { + final boolean shouldModify = level.chunkPacketBlockController.shouldModify(packetListener.player, chunk); +- packetListener.send(new ClientboundLevelChunkWithLightPacket(chunk, level.getLightEngine(), null, null, shouldModify)); ++ // Leaf start - Async chunk send ++ if (org.dreeam.leaf.config.modules.async.AsyncChunkSend.enabled) { ++ var blockEntities = chunk.blockEntities.values().toArray(new net.minecraft.world.level.block.entity.BlockEntity[0]); ++ java.util.Map heightmaps = new java.util.concurrent.ConcurrentHashMap<>(); ++ ++ for (var entry : chunk.getHeightmaps()) { ++ if (entry.getKey().sendToClient()) { ++ heightmaps.put(entry.getKey(), entry.getValue().getRawData()); ++ } ++ } ++ org.dreeam.leaf.async.chunk.AsyncChunkSend.POOL.execute( ++ () -> packetListener.send(new ClientboundLevelChunkWithLightPacket(chunk, level.getLightEngine(), null, null, shouldModify, blockEntities, heightmaps)) ++ ); ++ } else { ++ packetListener.send(new ClientboundLevelChunkWithLightPacket(chunk, level.getLightEngine(), null, null, shouldModify)); ++ } ++ // Leaf end - Async chunk send + // Paper end - Anti-Xray + // Paper start - PlayerChunkLoadEvent + if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) { +diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java +index 36c033b0ee63dfc273d721fb4b614733e8fdef19..1cc33a038060aaf5258ee4f1deb19b4a1be59a29 100644 +--- a/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -18,7 +18,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ + public static final int SECTION_HEIGHT = 16; + public static final int SECTION_SIZE = 4096; + public static final int BIOME_CONTAINER_BITS = 2; +- short nonEmptyBlockCount; // Paper - package private ++ volatile short nonEmptyBlockCount; // Paper - package private // Leaf - Async chunk send - volatile + private short tickingBlockCount; + private short tickingFluidCount; + private boolean isRandomlyTickingBlocksStatus; // Leaf - Cache random tick block status diff --git a/leaf-server/minecraft-patches/features/0216-Async-chunk-sending.patch b/leaf-server/minecraft-patches/features/0216-Async-chunk-sending.patch deleted file mode 100644 index 4b7c3e38..00000000 --- a/leaf-server/minecraft-patches/features/0216-Async-chunk-sending.patch +++ /dev/null @@ -1,108 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Sun, 2 Mar 2025 21:23:20 +0100 -Subject: [PATCH] Async chunk sending - - -diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -index 32608df3da169159c070f37cb55407f4f6187744..67275e803e0287306b163f4eec17388b9c701a8c 100644 ---- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -+++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -@@ -409,19 +409,91 @@ public final class RegionizedPlayerChunkLoader { - this.delayedTicketOps.addLast(op); - } - -+ // Leaf start - Async chunk sending -+ /** -+ * Sends a chunk to the player. -+ * If async chunk sending is enabled, this will prepare and send the chunk packet asynchronously. -+ * Otherwise, it will use the synchronous chunk sending implementation. -+ */ - private void sendChunk(final int chunkX, final int chunkZ) { -- if (this.sentChunks.add(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -- ((ChunkSystemChunkHolder)((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager -- .getChunkHolder(chunkX, chunkZ).vanillaChunkHolder).moonrise$addReceivedChunk(this.player); -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); - -- final LevelChunk chunk = ((ChunkSystemLevel)this.world).moonrise$getFullChunkIfLoaded(chunkX, chunkZ); -+ if (!this.sentChunks.add(chunkKey)) { -+ // Already in our sent list - silently return instead of throwing an exception -+ return; -+ } -+ -+ // Get the chunk now, as we need it for both sync and async paths -+ final LevelChunk chunk = ((ChunkSystemLevel) this.world).moonrise$getFullChunkIfLoaded(chunkX, chunkZ); -+ if (chunk == null) { -+ // Handle case where chunk is no longer loaded -+ this.sentChunks.remove(chunkKey); -+ return; -+ } -+ -+ // Try to mark the chunk as received by this player -+ try { -+ // This part needs to remain on the main thread as it affects shared state -+ ((ChunkSystemServerLevel) this.world).moonrise$getChunkTaskScheduler().chunkHolderManager -+ .getChunkHolder(chunkX, chunkZ).vanillaChunkHolder.moonrise$addReceivedChunk(this.player); - -+ // Call onChunkWatch on the main thread as it might affect server state - PlatformHooks.get().onChunkWatch(this.world, chunk, this.player); -- PlayerChunkSender.sendChunk(this.player.connection, this.world, chunk); -+ } catch (IllegalStateException e) { -+ // This happens if the chunk was already marked as received by this player -+ // Just remove it from our sent list and return -+ this.sentChunks.remove(chunkKey); - return; - } -- throw new IllegalStateException(); -+ -+ // Check if async chunk sending is enabled -+ if (org.dreeam.leaf.config.modules.async.AsyncChunkSend.enabled) { -+ // Async implementation -+ net.minecraft.Util.backgroundExecutor().execute(() -> { -+ try { -+ final net.minecraft.server.network.ServerGamePacketListenerImpl connection = this.player.connection; -+ final ServerLevel serverLevel = this.world; -+ -+ // Create the packet with anti-xray control flag -+ final net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket packet = new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket( -+ chunk, serverLevel.getLightEngine(), null, null, -+ serverLevel.chunkPacketBlockController.shouldModify(this.player, chunk) -+ ); -+ -+ // Let the main thread handle the anti-xray processing -+ serverLevel.getServer().execute(() -> { -+ if (this.removed || !this.sentChunks.contains(chunkKey)) { -+ return; -+ } -+ -+ // This will trigger anti-xray processing and mark the packet as ready when done -+ // The packet automatically handles readiness -+ // Send the packet (which will be held until ready by the network layer) -+ connection.send(packet); -+ -+ // Fire events and send POI packets -+ if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) { -+ new io.papermc.paper.event.packet.PlayerChunkLoadEvent( -+ new org.bukkit.craftbukkit.CraftChunk(chunk), -+ this.player.getBukkitEntity() -+ ).callEvent(); -+ } -+ -+ net.minecraft.network.protocol.game.DebugPackets.sendPoiPacketsForChunk(serverLevel, chunk.getPos()); -+ }); -+ } catch (Exception e) { -+ org.dreeam.leaf.async.AsyncChunkSending.LOGGER.error("Failed to send chunk asynchronously!", e); -+ -+ if (!this.removed) { -+ this.sentChunks.remove(chunkKey); -+ } -+ } -+ }); -+ } else { -+ PlayerChunkSender.sendChunk(this.player.connection, this.world, chunk); -+ } - } -+ // Leaf end - Async chunk sending - - private void sendUnloadChunk(final int chunkX, final int chunkZ) { - if (!this.sentChunks.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { diff --git a/leaf-server/minecraft-patches/features/0217-Spawner-Configurations.patch b/leaf-server/minecraft-patches/features/0217-Spawner-Configurations.patch index a5108c8c..1d575fd9 100644 --- a/leaf-server/minecraft-patches/features/0217-Spawner-Configurations.patch +++ b/leaf-server/minecraft-patches/features/0217-Spawner-Configurations.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Spawner Configurations diff --git a/net/minecraft/world/level/BaseSpawner.java b/net/minecraft/world/level/BaseSpawner.java -index 9fa9d84033c28071120e8a1c796ace4f9a45d4c5..9914e347838f55b4658a0ab8998bc0eeade0f4f8 100644 +index 9fa9d84033c28071120e8a1c796ace4f9a45d4c5..dc1d0c8a5b26142eeb8b9c167cef8d3ddcc1ad55 100644 --- a/net/minecraft/world/level/BaseSpawner.java +++ b/net/minecraft/world/level/BaseSpawner.java @@ -55,6 +55,12 @@ public abstract class BaseSpawner { @@ -58,17 +58,21 @@ index 9fa9d84033c28071120e8a1c796ace4f9a45d4c5..9914e347838f55b4658a0ab8998bc0ee if (this.isNearPlayer(serverLevel, pos)) { if (this.spawnDelay < -tickDelay) { // Paper - Configurable mob spawner tick rate this.delay(serverLevel, pos); -@@ -112,18 +141,41 @@ public abstract class BaseSpawner { +@@ -112,18 +141,48 @@ public abstract class BaseSpawner { pos.getZ() + (random.nextDouble() - random.nextDouble()) * this.spawnRange + 0.5 ) ); - if (serverLevel.noCollision(optional.get().getSpawnAABB(vec3.x, vec3.y, vec3.z))) { -+ // Leaf start - Spawner Configurations ++ // Leaf start - Spawner Configurations + // Skip collision check if block checks are disabled + boolean skipBlockChecks = org.dreeam.leaf.config.modules.gameplay.SpawnerSettings.enabled && + !org.dreeam.leaf.config.modules.gameplay.SpawnerSettings.spawnerBlockChecks; + if (skipBlockChecks || serverLevel.noCollision(optional.get().getSpawnAABB(vec3.x, vec3.y, vec3.z))) { ++ // 'skipBlockChecks' is true if SpawnerSettings.spawnerBlockChecks is false. ++ // It means we skip physical block checks like collision and custom rule isValidPosition. ++ BlockPos blockPos = BlockPos.containing(vec3); ++ + // Add light level check if enabled + if (org.dreeam.leaf.config.modules.gameplay.SpawnerSettings.enabled && + org.dreeam.leaf.config.modules.gameplay.SpawnerSettings.lightLevelCheck) { @@ -85,8 +89,9 @@ index 9fa9d84033c28071120e8a1c796ace4f9a45d4c5..9914e347838f55b4658a0ab8998bc0ee + continue; + } + -+ // Handle spawn rules checks -+ boolean skipSpawnRules = false; ++ // Determine if mob-specific spawn rules (like block types, biome requirements) should be skipped ++ boolean skipMobSpecificRules = org.dreeam.leaf.config.modules.gameplay.SpawnerSettings.enabled && ++ org.dreeam.leaf.config.modules.gameplay.SpawnerSettings.ignoreSpawnRules; + // Leaf end - Spawner Configurations if (nextSpawnData.getCustomSpawnRules().isPresent()) { if (!optional.get().getCategory().isFriendly() && serverLevel.getDifficulty() == Difficulty.PEACEFUL) { @@ -95,15 +100,17 @@ index 9fa9d84033c28071120e8a1c796ace4f9a45d4c5..9914e347838f55b4658a0ab8998bc0ee SpawnData.CustomSpawnRules customSpawnRules = nextSpawnData.getCustomSpawnRules().get(); - if (!customSpawnRules.isValidPosition(blockPos, serverLevel)) { ++ // customSpawnRules.isValidPosition is controlled by spawnerBlockChecks (via !skipBlockChecks) + if (!skipBlockChecks && !customSpawnRules.isValidPosition(blockPos, serverLevel)) { // Leaf - Spawner Configurations continue; } - } else if (!SpawnPlacements.checkSpawnRules(optional.get(), serverLevel, EntitySpawnReason.SPAWNER, blockPos, serverLevel.getRandom())) { -+ } else if (!skipBlockChecks && !SpawnPlacements.checkSpawnRules(optional.get(), serverLevel, EntitySpawnReason.SPAWNER, blockPos, serverLevel.getRandom())) { // Leaf - Spawner Configurations ++ } else if (!skipMobSpecificRules && !SpawnPlacements.checkSpawnRules(optional.get(), serverLevel, EntitySpawnReason.SPAWNER, blockPos, serverLevel.getRandom())) { ++ // If not skipping mob-specific rules AND standard spawn rules fail, continue. continue; } -@@ -151,6 +203,7 @@ public abstract class BaseSpawner { +@@ -151,6 +210,7 @@ public abstract class BaseSpawner { return; } @@ -111,7 +118,7 @@ index 9fa9d84033c28071120e8a1c796ace4f9a45d4c5..9914e347838f55b4658a0ab8998bc0ee int size = serverLevel.getEntities( EntityTypeTest.forExactClass(entity.getClass()), new AABB(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1).inflate(this.spawnRange), -@@ -161,12 +214,16 @@ public abstract class BaseSpawner { +@@ -161,12 +221,29 @@ public abstract class BaseSpawner { this.delay(serverLevel, pos); return; } @@ -123,14 +130,27 @@ index 9fa9d84033c28071120e8a1c796ace4f9a45d4c5..9914e347838f55b4658a0ab8998bc0ee - if (nextSpawnData.getCustomSpawnRules().isEmpty() && !mob.checkSpawnRules(serverLevel, EntitySpawnReason.SPAWNER) - || !mob.checkSpawnObstruction(serverLevel)) { + // Leaf start - Spawner Configurations -+ // Skip spawn rule and obstruction checks if block checks are disabled -+ if (!skipBlockChecks && (nextSpawnData.getCustomSpawnRules().isEmpty() && !mob.checkSpawnRules(serverLevel, EntitySpawnReason.SPAWNER) -+ || !mob.checkSpawnObstruction(serverLevel))) { ++ // mob.checkSpawnRules is controlled by ignoreSpawnRules (via !skipMobSpecificRules) ++ // mob.checkSpawnObstruction is controlled by spawnerBlockChecks (via !skipBlockChecks) ++ ++ boolean mobSpecificRulesFailed = false; ++ if (nextSpawnData.getCustomSpawnRules().isEmpty() && !skipMobSpecificRules) { ++ if (!mob.checkSpawnRules(serverLevel, EntitySpawnReason.SPAWNER)) { ++ mobSpecificRulesFailed = true; ++ } ++ } ++ ++ boolean obstructionFailed = false; ++ if (!skipBlockChecks && !mob.checkSpawnObstruction(serverLevel)) { // If not skipping physical checks and obstruction fails ++ obstructionFailed = true; ++ } ++ ++ if (mobSpecificRulesFailed || obstructionFailed) { + // Leaf end - Spawner Configurations continue; } -@@ -231,10 +288,16 @@ public abstract class BaseSpawner { +@@ -231,10 +308,16 @@ public abstract class BaseSpawner { tag.read("SpawnData", SpawnData.CODEC).ifPresent(spawnData -> this.setNextSpawnData(level, pos, spawnData)); this.spawnPotentials = tag.read("SpawnPotentials", SpawnData.LIST_CODEC) .orElseGet(() -> WeightedList.of(this.nextSpawnData != null ? this.nextSpawnData : new SpawnData())); diff --git a/leaf-server/minecraft-patches/features/0225-Optimize-addOrUpdateTransientModifier.patch b/leaf-server/minecraft-patches/features/0225-Optimize-addOrUpdateTransientModifier.patch index 9bea29b1..2bb178ef 100644 --- a/leaf-server/minecraft-patches/features/0225-Optimize-addOrUpdateTransientModifier.patch +++ b/leaf-server/minecraft-patches/features/0225-Optimize-addOrUpdateTransientModifier.patch @@ -5,10 +5,10 @@ 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 11520972f4fabde3be48edd296351113453b2869..5fe88b105efd3546c675b3397be46bf42e830fb3 100644 +index 3ac9f36eae87369354e992a1d9b5c5b2d87d17cb..c39654d3e1e5573646b3729502e4eae1a6dba7c9 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 { +@@ -87,8 +87,13 @@ public class AttributeInstance { } public void addOrUpdateTransientModifier(AttributeModifier modifier) { diff --git a/leaf-server/minecraft-patches/features/0236-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0235-Async-target-finding.patch similarity index 75% rename from leaf-server/minecraft-patches/features/0236-Async-target-finding.patch rename to leaf-server/minecraft-patches/features/0235-Async-target-finding.patch index 8c3f3abd..9cae915b 100644 --- a/leaf-server/minecraft-patches/features/0236-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0235-Async-target-finding.patch @@ -4,6 +4,107 @@ Date: Sat, 29 Mar 2025 13:40:46 +0100 Subject: [PATCH] Async target finding +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..956d48fb7146b9eb2a5b5b4e23a83f60d0e40b4c 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java +@@ -40,8 +40,10 @@ public final class ChunkEntitySlices { + + private final EntityCollectionBySection allEntities; + private final EntityCollectionBySection hardCollidingEntities; +- private final Reference2ObjectOpenHashMap, EntityCollectionBySection> entitiesByClass; +- private final Reference2ObjectOpenHashMap, EntityCollectionBySection> entitiesByType; ++ // Leaf start - Async target finding ++ private final Reference2ObjectMap, EntityCollectionBySection> entitiesByClass; ++ private final Reference2ObjectMap, EntityCollectionBySection> entitiesByType; ++ // Leaf end - Async target finding + private final EntityList entities = new EntityList(); + + public FullChunkStatus status; +@@ -67,9 +69,15 @@ public final class ChunkEntitySlices { + + this.allEntities = new EntityCollectionBySection(this); + this.hardCollidingEntities = new EntityCollectionBySection(this); +- this.entitiesByClass = new Reference2ObjectOpenHashMap<>(); +- this.entitiesByType = new Reference2ObjectOpenHashMap<>(); +- ++ // Leaf start - Async target finding ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ this.entitiesByClass = it.unimi.dsi.fastutil.objects.Reference2ObjectMaps.synchronize(new Reference2ObjectOpenHashMap<>()); ++ this.entitiesByType = it.unimi.dsi.fastutil.objects.Reference2ObjectMaps.synchronize(new Reference2ObjectOpenHashMap<>()); ++ } else { ++ this.entitiesByClass = new Reference2ObjectOpenHashMap<>(); ++ this.entitiesByType = new Reference2ObjectOpenHashMap<>(); ++ } ++ // Leaf end - Async target finding + this.status = status; + this.chunkData = chunkData; + } +@@ -248,14 +256,26 @@ public final class ChunkEntitySlices { + this.hardCollidingEntities.addEntity(entity, sectionIndex); + } + +- for (final Iterator, EntityCollectionBySection>> iterator = +- this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { +- final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); ++ // Leaf start - Async target finding ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ synchronized (this.entitiesByClass) { ++ for (final var entry : this.entitiesByClass.reference2ObjectEntrySet()) { ++ if (entry.getKey().isInstance(entity)) { ++ entry.getValue().addEntity(entity, sectionIndex); ++ } ++ } ++ } ++ } else { ++ for (final Iterator, EntityCollectionBySection>> iterator = ++ this.entitiesByClass.reference2ObjectEntrySet().iterator(); iterator.hasNext(); ) { ++ final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); + +- if (entry.getKey().isInstance(entity)) { +- entry.getValue().addEntity(entity, sectionIndex); ++ if (entry.getKey().isInstance(entity)) { ++ entry.getValue().addEntity(entity, sectionIndex); ++ } + } + } ++ // Leaf end - Async target finding + + EntityCollectionBySection byType = this.entitiesByType.get(entity.getType()); + if (byType != null) { +@@ -282,14 +302,27 @@ public final class ChunkEntitySlices { + this.hardCollidingEntities.removeEntity(entity, sectionIndex); + } + +- for (final Iterator, EntityCollectionBySection>> iterator = +- this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { +- final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); ++ // Leaf start - Async target finding ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ synchronized (this.entitiesByClass) { ++ for (final var entry : this.entitiesByClass.reference2ObjectEntrySet()) { ++ if (entry.getKey().isInstance(entity)) { ++ entry.getValue().removeEntity(entity, sectionIndex); ++ } ++ } ++ } ++ } else { ++ for (final Iterator, EntityCollectionBySection>> iterator = ++ this.entitiesByClass.reference2ObjectEntrySet().iterator(); iterator.hasNext();) { ++ final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); + +- if (entry.getKey().isInstance(entity)) { +- entry.getValue().removeEntity(entity, sectionIndex); ++ if (entry.getKey().isInstance(entity)) { ++ entry.getValue().removeEntity(entity, sectionIndex); ++ } + } + } ++ // Leaf end - Async target finding ++ + + final EntityCollectionBySection byType = this.entitiesByType.get(entity.getType()); + byType.removeEntity(entity, sectionIndex); diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java index 508f3c7c1e806576f1faea7975ac9bbe73b1024b..78aee57ad8224b0728411c699d2e3844847c9c79 100644 --- a/net/minecraft/server/MinecraftServer.java @@ -48,7 +149,7 @@ index 54910c2e1d6e6bb556e536fda060bd09402e04e8..747eb54f84650a9a507398e3d5352e00 // Gale start - Pufferfish - SIMD support diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 461b620b703c9fca2691f724a9b865ad54aa90a4..8ade900d016026cde482ccbca7a411993d9eadd9 100644 +index 461b620b703c9fca2691f724a9b865ad54aa90a4..48e06770ee9789e0b64e7369e26f23eea72f691a 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -173,7 +173,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -115,7 +216,7 @@ index 461b620b703c9fca2691f724a9b865ad54aa90a4..8ade900d016026cde482ccbca7a41199 } ); this.tickBlockEntities(); -+ if (this.asyncGoalExecutor != null) this.asyncGoalExecutor.unpark(); // Leaf - Async target finding ++ if (this.asyncGoalExecutor != null) this.asyncGoalExecutor.tick(); // Leaf - Async target finding } // Paper - rewrite chunk system } @@ -135,62 +236,8 @@ index 461b620b703c9fca2691f724a9b865ad54aa90a4..8ade900d016026cde482ccbca7a41199 } // 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 +index 77075bf43e7e2301e58aa6d9f8bfe934cce42f55..edd9de6992489404f421164dd25edc4023fb14d6 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 @@ -206,21 +253,22 @@ index 77075bf43e7e2301e58aa6d9f8bfe934cce42f55..4a21420a3c7112024aba1a91bf635872 protected Mob(EntityType entityType, Level level) { super(entityType, level); -@@ -213,12 +219,21 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab +@@ -213,12 +219,22 @@ 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 ++ // Leaf start - Async target finding ++ boolean running = this.targetSelector.ctxState != -1 || this.goalSelector.ctxState != -1; ++ this.tickingTarget = false; 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 ++ this.tickingTarget = true; 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) { ++ if (!running && (this.targetSelector.ctxState != -1 || this.goalSelector.ctxState != -1)) { + ((ServerLevel) this.level()).asyncGoalExecutor.submit(this.getId()); + } + } @@ -228,11 +276,12 @@ index 77075bf43e7e2301e58aa6d9f8bfe934cce42f55..4a21420a3c7112024aba1a91bf635872 } // Paper end -@@ -765,17 +780,28 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab +@@ -765,17 +781,29 @@ 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 ++ boolean running = this.targetSelector.ctxState != -1 || this.goalSelector.ctxState != -1; if (i % 2 != 0 && this.tickCount > 1) { + this.tickingTarget = true; if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking @@ -249,7 +298,7 @@ index 77075bf43e7e2301e58aa6d9f8bfe934cce42f55..4a21420a3c7112024aba1a91bf635872 this.goalSelector.tick(); } + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { -+ if (this.targetSelector.ctxGoals != null || this.goalSelector.ctxGoals != null) { ++ if (!running && (this.targetSelector.ctxState != -1 || this.goalSelector.ctxState != -1)) { + ((ServerLevel) this.level()).asyncGoalExecutor.submit(this.getId()); + } + } @@ -258,7 +307,7 @@ index 77075bf43e7e2301e58aa6d9f8bfe934cce42f55..4a21420a3c7112024aba1a91bf635872 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 +index 7651676e72fcec52d7c1f9f7d7b6f9e585015c4d..ed3c5ad51ec0b9a1997e732e11db99ce06db7307 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 { @@ -287,7 +336,7 @@ index 7651676e72fcec52d7c1f9f7d7b6f9e585015c4d..28aae1f1115c3a5dc5b3b585621ce32e if (this.toAvoid == null) { return false; } else { -@@ -91,6 +100,45 @@ public class AvoidEntityGoal extends Goal { +@@ -91,6 +100,36 @@ public class AvoidEntityGoal extends Goal { } } @@ -310,23 +359,14 @@ index 7651676e72fcec52d7c1f9f7d7b6f9e585015c4d..28aae1f1115c3a5dc5b3b585621ce32e + 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); -+ } -+ }; ++ ctx.wake = () -> serverLevel.getNearestEntity( ++ serverLevel.getEntitiesOfClass(avoidClass, bound, livingEntity -> true), ++ avoidEntityTargeting, ++ mob, ++ x, ++ y, ++ z ++ ); + } + // Leaf end - Async Avoid Entity Finding + @@ -334,10 +374,10 @@ index 7651676e72fcec52d7c1f9f7d7b6f9e585015c4d..28aae1f1115c3a5dc5b3b585621ce32e 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 +index 6f7767d1ef5ee20338a334d85ad58dab9f8c4d1b..9f2658f49640c4f5c2de0c2810b3042b385f688a 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 { +@@ -27,8 +27,43 @@ public class BegGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.LOOK)); } @@ -358,18 +398,11 @@ index 6f7767d1ef5ee20338a334d85ad58dab9f8c4d1b..d5a8d0fc6d28e9cd770b76a569fd73d2 + 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); ++ var player = serverLevel.getNearestPlayer(begTargeting, wolf); ++ if (player != null && playerHoldingInteresting(player)) { ++ return player; + } ++ return null; + }; + } + // Leaf end - Async Target Finding @@ -388,7 +421,7 @@ index 6f7767d1ef5ee20338a334d85ad58dab9f8c4d1b..d5a8d0fc6d28e9cd770b76a569fd73d2 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 { +@@ -59,10 +94,10 @@ public class BegGoal extends Goal { this.lookTime--; } @@ -449,10 +482,10 @@ index 9954f49bc364969c7ccb37f4186fa2ab8710f6ae..057090e3134048e75dbaefb703e8f2d3 + // 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 +index f7dcd341444059f8fb9708e9a21b5d8ace6b9cf9..b5571b7deb1645d88493c9fae866fd3726666754 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 { +@@ -23,8 +23,47 @@ public class FollowBoatGoal extends Goal { this.mob = mob; } @@ -470,23 +503,16 @@ index f7dcd341444059f8fb9708e9a21b5d8ace6b9cf9..11c428833d43f867f9598093b13fe8bd + 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)) { ++ return player; + -+ 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); + } ++ return null; + }; + } + // Leaf end - Async Target Finding @@ -507,7 +533,7 @@ index f7dcd341444059f8fb9708e9a21b5d8ace6b9cf9..11c428833d43f867f9598093b13fe8bd 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 { +@@ -36,7 +75,7 @@ public class FollowBoatGoal extends Goal { } } @@ -517,7 +543,7 @@ index f7dcd341444059f8fb9708e9a21b5d8ace6b9cf9..11c428833d43f867f9598093b13fe8bd @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 +index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..c2baf746a0697559dc391b6f8c9303917e194836 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 { @@ -536,7 +562,7 @@ index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..911ceddb4ded974a02355c76e5d41336 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 { +@@ -51,6 +60,36 @@ public class FollowMobGoal extends Goal { return false; } @@ -557,22 +583,15 @@ index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..911ceddb4ded974a02355c76e5d41336 + 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; -+ } ++ List entitiesOfClass = serverLevel.getEntitiesOfClass(Mob.class, bound, followPredicate); ++ if (!entitiesOfClass.isEmpty()) { ++ for (final Mob follow : entitiesOfClass) { ++ if (!follow.isInvisible()) { ++ return follow; + } + } -+ } catch (Exception e) { -+ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } ++ return null; + }; + } + // Leaf end - Async Follow Mob Finding @@ -581,10 +600,10 @@ index c9cf985173d3da9e84ce178f161e2fd5fb8ce472..911ceddb4ded974a02355c76e5d41336 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 +index 3093f03d4f298bf39fec8bad2b6c22518774aea8..0a41797fd7beddce0b93d42bac6e0270169330ef 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 { +@@ -19,11 +19,56 @@ public class FollowParentGoal extends Goal { this.speedModifier = speedModifier; } @@ -606,30 +625,20 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..80f9de7b2c03c1477dae0f42b328e010 + final var serverLevel = getServerLevel(animal); + final var pos = animal.position(); + ctx.wake = () -> { -+ try { -+ if (animal.level() != serverLevel || animal.isRemoved() || !animal.isAlive()) { -+ return; -+ } ++ List entitiesOfClass = serverLevel.getEntitiesOfClass(targetType, bound); ++ Animal target = null; ++ double d = Double.MAX_VALUE; + -+ List entitiesOfClass = serverLevel.getEntitiesOfClass(targetType, bound); -+ Animal target = null; -+ double d = Double.MAX_VALUE; -+ -+ for (Animal animal1 : entitiesOfClass) { -+ if (animal1.getAge() >= 0) { -+ double d1 = animal1.distanceToSqr(pos); -+ if (!(d1 > d)) { -+ d = d1; -+ target = animal1; -+ } ++ 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); + } ++ return target; + }; + } + // Leaf end - Async Target Finding @@ -651,7 +660,7 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..80f9de7b2c03c1477dae0f42b328e010 List entitiesOfClass = this.animal .level() .getEntitiesOfClass((Class)this.animal.getClass(), this.animal.getBoundingBox().inflate(8.0, 4.0, 8.0)); -@@ -43,6 +98,7 @@ public class FollowParentGoal extends Goal { +@@ -43,6 +88,7 @@ public class FollowParentGoal extends Goal { if (animal == null) { return false; } else if (d < 9.0) { @@ -660,24 +669,56 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..80f9de7b2c03c1477dae0f42b328e010 } 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 +index e82e32407cec6109b9c3b0106295217f4a3f4aa2..3c24382a3cced8dcea103ccc87cb506310de8461 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 { +@@ -26,13 +26,23 @@ 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 boolean availableGoalsDirty = true; ++ private WrappedGoal @org.jetbrains.annotations.Nullable[] ctxGoals = null; + private int ctxIndex = 0; -+ private int ctxState = 0; ++ public int ctxState = -1; + 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)); ++ availableGoalsDirty = true; // Leaf - Async target finding } -@@ -85,7 +92,111 @@ public class GoalSelector { + + @VisibleForTesting + public void removeAllGoals(Predicate filter) { + this.availableGoals.removeIf(wrappedGoal -> filter.test(wrappedGoal.getGoal())); ++ availableGoalsDirty = true; // Leaf - Async target finding + } + + // Paper start - EAR 2 +@@ -63,18 +73,19 @@ public class GoalSelector { + } + + this.availableGoals.removeIf(wrappedGoal1 -> wrappedGoal1.getGoal() == goal); ++ availableGoalsDirty = true; // Leaf - Async target finding + } + + // Paper start - Perf: optimize goal types + private static boolean goalContainsAnyFlags(WrappedGoal goal, ca.spottedleaf.moonrise.common.set.OptimizedSmallEnumSet flags) { +- return goal.getFlags().hasCommonElements(flags); ++ return goal.getFlags().hasCommonElements(flags); // Leaf - Async target finding - inline diff + } + + private static boolean goalCanBeReplacedForAllFlags(WrappedGoal goal, Map flag) { + long flagIterator = goal.getFlags().getBackingSet(); + int wrappedGoalSize = goal.getFlags().size(); + for (int i = 0; i < wrappedGoalSize; ++i) { +- final Goal.Flag flag1 = GOAL_FLAG_VALUES[Long.numberOfTrailingZeros(flagIterator)]; ++ final Goal.Flag flag1 = GOAL_FLAG_VALUES[Long.numberOfTrailingZeros(flagIterator)]; // Leaf - Async target finding - inline diff + flagIterator ^= ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit(flagIterator); + // Paper end - Perf: optimize goal types + if (!flag.getOrDefault(flag1, NO_GOAL).canBeReplacedBy(goal)) { +@@ -85,7 +96,131 @@ public class GoalSelector { return true; } @@ -711,27 +752,39 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..ec56b4e8acd2dad23a94b1fe9145e625 + if (ctxState == 1) { + while (ctxIndex < this.ctxGoals.length) { + WrappedGoal goal = this.ctxGoals[ctxIndex]; ++ var flags = goal.getFlags(); + // 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); ++ if (!goal.isRunning() && !flags.hasCommonElements(this.goalTypes)) { ++ // inline ++ boolean result = true; ++ long flagIterator1 = flags.getBackingSet(); ++ int wrappedGoalSize1 = flags.size(); ++ for (int i1 = 0; i1 < wrappedGoalSize1; ++i1) { ++ final Goal.Flag flag1 = GOAL_FLAG_VALUES[Long.numberOfTrailingZeros(flagIterator1)]; ++ flagIterator1 ^= ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit(flagIterator1); ++ if (!this.lockedFlags.getOrDefault(flag1, NO_GOAL).canBeReplacedBy(goal)) { ++ result = false; ++ break; + } -+ -+ goal.start(); + } -+ ctx.state = false; -+ if (ctx.wake != null) { -+ return true; ++ if (result) { ++ if (goal.canUse()) { ++ long flagIterator = flags.getBackingSet(); ++ int wrappedGoalSize = flags.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 @@ -759,28 +812,36 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..ec56b4e8acd2dad23a94b1fe9145e625 + ctxIndex++; + } + -+ ctxGoals = null; -+ ctxState = 0; ++ ctxState = -1; ++ ctxIndex = 0; ++ ctx.state = true; ++ } ++ if (ctxState == 3) { ++ while (ctxIndex < this.ctxGoals.length) { ++ WrappedGoal goal = this.ctxGoals[ctxIndex]; ++ if (goal.isRunning() && goal.requiresUpdateEveryTick()) { ++ goal.tick(); ++ } ++ ctxIndex++; ++ } ++ ++ ctxState = -1; + 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]); ++ if (ctxState == -1) { ++ if (availableGoalsDirty || this.ctxGoals == null) { ++ this.ctxGoals = this.availableGoals.toArray(new WrappedGoal[0]); ++ availableGoalsDirty = false; ++ } ++ ctxState = 0; + } + return; + } @@ -789,11 +850,30 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..ec56b4e8acd2dad23a94b1fe9145e625 for (WrappedGoal wrappedGoal : this.availableGoals) { if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.goalTypes) || !wrappedGoal.canContinueToUse())) { // Paper - Perf: optimize goal types by removing streams wrappedGoal.stop(); +@@ -116,6 +251,18 @@ public class GoalSelector { + } + + public void tickRunningGoals(boolean tickAllRunning) { ++ // Leaf start - Async target finding ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ if (ctxState == -1) { ++ if (availableGoalsDirty || this.ctxGoals == null) { ++ this.ctxGoals = this.availableGoals.toArray(new WrappedGoal[0]); ++ availableGoalsDirty = false; ++ } ++ ctxState = tickAllRunning ? 2 : 3; ++ } ++ return; ++ } ++ // Leaf end - Async target finding + for (WrappedGoal wrappedGoal : this.availableGoals) { + if (wrappedGoal.isRunning() && (tickAllRunning || wrappedGoal.requiresUpdateEveryTick())) { + wrappedGoal.tick(); diff --git a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java -index be59d0c27a83b329ec3f97c029cfb9c114e22472..7c54964a5329e12a9a4a1fdb2417ccbd3f0bfae2 100644 +index be59d0c27a83b329ec3f97c029cfb9c114e22472..86f041ff21cf44a0cded5744055654a0bff40c42 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 { +@@ -20,20 +20,83 @@ public class LlamaFollowCaravanGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.MOVE)); } @@ -813,21 +893,28 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..7c54964a5329e12a9a4a1fdb2417ccbd + 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; ++ } + } ++ } + -+ 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()) { ++ 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; @@ -835,25 +922,8 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..7c54964a5329e12a9a4a1fdb2417ccbd + } + } + } -+ -+ 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); + } ++ return target; + }; + } + // Leaf end - Async Target Finding @@ -890,7 +960,7 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..7c54964a5329e12a9a4a1fdb2417ccbd double d1 = this.llama.distanceToSqr(llama1); if (!(d1 > d)) { d = d1; -@@ -45,7 +118,7 @@ public class LlamaFollowCaravanGoal extends Goal { +@@ -45,7 +108,7 @@ public class LlamaFollowCaravanGoal extends Goal { if (llama == null) { for (Entity entityx : entities) { Llama llama1 = (Llama)entityx; @@ -899,7 +969,7 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..7c54964a5329e12a9a4a1fdb2417ccbd double d1 = this.llama.distanceToSqr(llama1); if (!(d1 > d)) { d = d1; -@@ -54,6 +127,7 @@ public class LlamaFollowCaravanGoal extends Goal { +@@ -54,6 +117,7 @@ public class LlamaFollowCaravanGoal extends Goal { } } } @@ -908,10 +978,10 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..7c54964a5329e12a9a4a1fdb2417ccbd 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 +index 6463c3c9b08d6058f2843c225b08a40fc30a960b..98c2b4a298ada4b02afa55f991791d8696702181 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 { +@@ -48,32 +48,79 @@ public class LookAtPlayerGoal extends Goal { @Override public boolean canUse() { @@ -951,28 +1021,15 @@ index 6463c3c9b08d6058f2843c225b08a40fc30a960b..0afad8ddb0e9d083d47b96627d999f0e - 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; @@ -991,27 +1048,31 @@ index 6463c3c9b08d6058f2843c225b08a40fc30a960b..0afad8ddb0e9d083d47b96627d999f0e + 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); -+ } ++ if (lookAtType == Player.class) { ++ return serverLevel.getNearestPlayer(lookAtContext, mob, x, y, z); + } 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 serverLevel.getNearestEntity( ++ serverLevel ++ .getEntitiesOfClass(lookAtType, bound, livingEntity -> true), ++ lookAtContext, ++ mob, ++ x, ++ y, ++ z + ); + } +- +- return this.lookAt != null; +- } + }; } + // Leaf end - Async look finding @@ -1019,10 +1080,10 @@ index 6463c3c9b08d6058f2843c225b08a40fc30a960b..0afad8ddb0e9d083d47b96627d999f0e @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 +index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..9b8453ce2bc2cafca7c670d79b40434e7c93afca 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 { +@@ -41,8 +41,60 @@ public abstract class MoveToBlockGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.JUMP)); } @@ -1057,16 +1118,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..292749c786b85f86fa5f65c49fa83539 + 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); -+ } -+ }; ++ ctx.wake = () -> findNearestBlockAsync(ty, toRemove, mob, serverLevel, verticalSearchStart, searchRange, verticalSearchRange, blockPos, restrictRadius, restrictCenter); + } + + protected enum TypeToCheck { @@ -1092,7 +1144,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..292749c786b85f86fa5f65c49fa83539 if (this.nextStartTick > 0) { this.nextStartTick--; return false; -@@ -109,6 +170,12 @@ public abstract class MoveToBlockGoal extends Goal { +@@ -109,6 +161,12 @@ public abstract class MoveToBlockGoal extends Goal { } protected boolean findNearestBlock() { @@ -1105,13 +1157,12 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..292749c786b85f86fa5f65c49fa83539 int i = this.searchRange; int i1 = this.verticalSearchRange; BlockPos blockPos = this.mob.blockPosition(); -@@ -133,5 +200,108 @@ public abstract class MoveToBlockGoal extends Goal { +@@ -133,5 +191,105 @@ 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, ++ protected static @javax.annotation.Nullable BlockPos findNearestBlockAsync( + final TypeToCheck ty, + @org.jetbrains.annotations.Nullable final net.minecraft.world.level.block.Block toRemove, + final PathfinderMob mob, @@ -1132,15 +1183,13 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..292749c786b85f86fa5f65c49fa83539 + if (!serverLevel.hasChunkAt(mutableBlockPos)) continue; + if (isWithinRestriction(restrictRadius, restrictCenter, mutableBlockPos) + && isValidTargetAsync(ty, toRemove, mob, serverLevel, mutableBlockPos)) { -+ ctx.result = mutableBlockPos.immutable(); -+ return true; ++ return mutableBlockPos.immutable(); + } + } + } + } + } -+ -+ return false; ++ return null; + } + + private static boolean isWithinRestriction(float restrictRadius, BlockPos restrictCenter, BlockPos pos) { @@ -1215,7 +1264,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..292749c786b85f86fa5f65c49fa83539 + } } diff --git a/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java b/net/minecraft/world/entity/ai/goal/OfferFlowerGoal.java -index 4ba5f7da27f7f6842790c0093bc0f25e138fa982..1dcae83aa6b9e9318d320a17bf17a0b0eae80a60 100644 +index 4ba5f7da27f7f6842790c0093bc0f25e138fa982..0f2841a9681997c0d4b915284f7f5b385b1e172c 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 { @@ -1231,16 +1280,16 @@ index 4ba5f7da27f7f6842790c0093bc0f25e138fa982..1dcae83aa6b9e9318d320a17bf17a0b0 + return true; + } + if (this.golem.getRandom().nextInt(8000) != 0) { -+ return false; + return false; + } + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + getVillagerAsync(); - return false; -+ // Leaf end - Async offer flower finding ++ return false; ++ // Leaf end - Async offer flower finding } else { this.villager = getServerLevel(this.golem) .getNearestEntity( -@@ -38,6 +48,45 @@ public class OfferFlowerGoal extends Goal { +@@ -38,6 +48,36 @@ public class OfferFlowerGoal extends Goal { } } @@ -1263,23 +1312,14 @@ index 4ba5f7da27f7f6842790c0093bc0f25e138fa982..1dcae83aa6b9e9318d320a17bf17a0b0 + 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); -+ } -+ }; ++ ctx.wake = () -> serverLevel.getNearestEntity( ++ serverLevel.getEntitiesOfClass(Villager.class, bound, livingEntity -> true), ++ OFFER_TARGER_CONTEXT, ++ golem, ++ x, ++ y, ++ z ++ ); + } + // Leaf end - Async look finding + @@ -1324,10 +1364,10 @@ index 084156166905562beb8d36db400bc9cab5c14be2..3eed1ac1cab130034f47da6efa33c0ad + // 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 +index f88f618d34fb343b31de3af1a875d6633703df71..754c379b42cf65c1d2278b474cdfbe50e9e62b34 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 { +@@ -36,12 +36,51 @@ public class TemptGoal extends Goal { this.targetingConditions = TEMPT_TARGETING.copy().selector((entity, level) -> this.shouldFollow(entity)); } @@ -1348,16 +1388,7 @@ index f88f618d34fb343b31de3af1a875d6633703df71..e42f77fc6d04a900fd8adb4682678f92 + 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); -+ } -+ }; ++ ctx.wake = () -> serverLevel.getNearestPlayer(conditions, mob); + } + // Leaf end - Async Tempt Finding @Override @@ -1389,7 +1420,7 @@ index f88f618d34fb343b31de3af1a875d6633703df71..e42f77fc6d04a900fd8adb4682678f92 .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 +index fb160a59c873d5c3f2c3d31966ca1a653f1b384d..6e752caa969891fa7f44b69ab58fca5434bb1aa9 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 { @@ -1401,7 +1432,7 @@ index fb160a59c873d5c3f2c3d31966ca1a653f1b384d..47802c05cec6a0d8b64a592332d71259 public DefendVillageTargetGoal(IronGolem golem) { super(golem, false, true); -@@ -24,8 +24,58 @@ public class DefendVillageTargetGoal extends TargetGoal { +@@ -24,8 +24,49 @@ public class DefendVillageTargetGoal extends TargetGoal { this.setFlags(EnumSet.of(Goal.Flag.TARGET)); } @@ -1422,26 +1453,17 @@ index fb160a59c873d5c3f2c3d31966ca1a653f1b384d..47802c05cec6a0d8b64a592332d71259 + 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; -+ } ++ 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) { ++ return player; + } + } -+ } catch (Exception e) { -+ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception getting entities", e); + } ++ return null; + }; + } + // Leaf end - Async Target Finding @@ -1460,7 +1482,7 @@ index fb160a59c873d5c3f2c3d31966ca1a653f1b384d..47802c05cec6a0d8b64a592332d71259 AABB aabb = this.golem.getBoundingBox().inflate(10.0, 8.0, 10.0); ServerLevel serverLevel = getServerLevel(this.golem); List nearbyEntities = serverLevel.getNearbyEntities(Villager.class, this.attackTargeting, this.golem, aabb); -@@ -42,7 +92,7 @@ public class DefendVillageTargetGoal extends TargetGoal { +@@ -42,7 +83,7 @@ public class DefendVillageTargetGoal extends TargetGoal { } } @@ -1470,10 +1492,10 @@ index fb160a59c873d5c3f2c3d31966ca1a653f1b384d..47802c05cec6a0d8b64a592332d71259 @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 +index a8ec1d5f4b0fb0ff26a234235b7d8d9c4b4a2a98..212934d7a2adc20c808ab204232dfa1ec99988b7 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 { +@@ -73,6 +73,46 @@ public class HurtByTargetGoal extends TargetGoal { protected void alertOthers() { double followDistance = this.getFollowDistance(); AABB aabb = AABB.unitCubeFromLowerCorner(this.mob.position()).inflate(followDistance, 10.0, followDistance); @@ -1486,42 +1508,32 @@ index a8ec1d5f4b0fb0ff26a234235b7d8d9c4b4a2a98..24317ff50332e624a5587a0273082005 + 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 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())) { ++ continue; + } -+ var toAlert = new java.util.ArrayList(); -+ List entitiesOfClass = serverLevel -+ .getEntitiesOfClass(self.getClass(), aabb, EntitySelector.NO_SPECTATORS); -+ for (Mob mob : entitiesOfClass) { -+ if (self != mob -+ && mob.getTarget() == null -+ && (!(self instanceof TamableAnimal) || ((TamableAnimal) self).getOwner() == ((TamableAnimal) mob).getOwner()) -+ && !mob.isAlliedTo(self.getLastHurtByMob())) { -+ if (toIgnoreAlert == null) { -+ continue; -+ } -+ -+ boolean flag = false; -+ -+ for (Class clazz : toIgnoreAlert) { -+ if (mob.getClass() == clazz) { -+ flag = true; -+ break; -+ } -+ } -+ -+ if (!flag) { -+ continue; -+ } -+ -+ toAlert.add(mob); ++ if (toIgnoreAlert == null) { ++ toAlert.add(mob); ++ continue; ++ } ++ boolean flag = false; ++ for (Class clazz : toIgnoreAlert) { ++ if (mob.getClass() == clazz) { ++ flag = true; ++ break; + } + } -+ ctx.result = toAlert; -+ } catch (Exception e) { -+ org.dreeam.leaf.async.ai.AsyncGoalExecutor.LOGGER.error("Exception alertOthers", e); ++ if (!flag) { ++ toAlert.add(mob); ++ } + } ++ return toAlert; + }; + return; + } @@ -1530,7 +1542,7 @@ index a8ec1d5f4b0fb0ff26a234235b7d8d9c4b4a2a98..24317ff50332e624a5587a0273082005 List entitiesOfClass = this.mob .level() .getEntitiesOfClass((Class)this.mob.getClass(), aabb, EntitySelector.NO_SPECTATORS); -@@ -87,7 +137,7 @@ public class HurtByTargetGoal extends TargetGoal { +@@ -87,7 +127,7 @@ public class HurtByTargetGoal extends TargetGoal { mob = (Mob)var5.next(); if (this.mob != mob @@ -1539,7 +1551,7 @@ index a8ec1d5f4b0fb0ff26a234235b7d8d9c4b4a2a98..24317ff50332e624a5587a0273082005 && (!(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 { +@@ -96,7 +136,7 @@ public class HurtByTargetGoal extends TargetGoal { boolean flag = false; @@ -1548,15 +1560,36 @@ index a8ec1d5f4b0fb0ff26a234235b7d8d9c4b4a2a98..24317ff50332e624a5587a0273082005 if (mob.getClass() == clazz) { flag = true; break; -@@ -113,6 +163,15 @@ public class HurtByTargetGoal extends TargetGoal { +@@ -113,6 +153,36 @@ 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()); ++ LivingEntity lastHurtByMob = this.mob.getLastHurtByMob(); ++ if (lastHurtByMob.getType() == EntityType.PLAYER && getServerLevel(this.mob).getGameRules().getBoolean(GameRules.RULE_UNIVERSAL_ANGER)) { ++ return; ++ } ++ for (Class clazz : this.toIgnoreDamage) { ++ if (clazz.isAssignableFrom(lastHurtByMob.getClass())) { ++ return; ++ } ++ } ++ if (this.mob.getTarget() == null) { ++ return; ++ } ++ if (!canContinueToUse()) { ++ return; ++ } ++ if (!this.canAttack(lastHurtByMob, HURT_BY_TARGETING)) { ++ return; ++ } ++ for (var obj : toAlert) { ++ Mob mob = (Mob) obj; ++ if (mob.getTarget() == null) { ++ alertOther(mob, lastHurtByMob); ++ } + } + } + // Leaf end - Async alert other @@ -1565,10 +1598,10 @@ index a8ec1d5f4b0fb0ff26a234235b7d8d9c4b4a2a98..24317ff50332e624a5587a0273082005 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 +index 1cd8143c938237ce035fa866d4f2ed1e2cd1ebca..211178240d9f58504ff822e459f3e1139ffda32d 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 +@@ -41,8 +41,43 @@ public class NearestAttackableTargetGoal extends TargetG this.targetConditions = TargetingConditions.forCombat().range(this.getFollowDistance()).selector(selector); } @@ -1593,18 +1626,10 @@ index 1cd8143c938237ce035fa866d4f2ed1e2cd1ebca..719dbfb7c9f463767bbee33ecb9d764b + 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); ++ if (targetType != Player.class && targetType != ServerPlayer.class) { ++ return serverLevel.getNearestEntity(mob.level().getEntitiesOfClass(targetType, targetSearch, entity -> entity != null && entity != mob && entity.isAlive()), targetConditions, mob, x, y, z); ++ } else { ++ return serverLevel.getNearestPlayer(targetConditions, mob, x, y, z); + } + }; + } @@ -1620,7 +1645,7 @@ index 1cd8143c938237ce035fa866d4f2ed1e2cd1ebca..719dbfb7c9f463767bbee33ecb9d764b if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) { return false; } else { -@@ -57,6 +100,15 @@ public class NearestAttackableTargetGoal extends TargetG +@@ -57,6 +92,15 @@ public class NearestAttackableTargetGoal extends TargetG protected void findTarget() { ServerLevel serverLevel = getServerLevel(this.mob); @@ -1679,10 +1704,10 @@ index abf57494950f55bbd75f335f26736cb9e703c197..efd2418f56c36e7850edde483a2a4906 } } 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 +index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..270f0b8b33aed1c54edbdb8595ce7fcc7fca2dc4 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 +@@ -37,6 +37,27 @@ public class ResetUniversalAngerTargetGoal extends G this.lastHurtByPlayerTimestamp = this.mob.getLastHurtByMobTimestamp(); this.mob.forgetCurrentTargetAndRefreshUniversalAnger(); if (this.alertOthersOfSameType) { @@ -1695,21 +1720,14 @@ index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..15da909b4db3d7a7532aed550208ecbb + 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); + } -+ 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 toStop; + }; + return; + } @@ -1717,7 +1735,7 @@ index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..15da909b4db3d7a7532aed550208ecbb this.getNearbyMobsOfSameType() .stream() .filter(mob -> mob != this.mob) -@@ -47,7 +75,17 @@ public class ResetUniversalAngerTargetGoal extends G +@@ -47,7 +68,19 @@ public class ResetUniversalAngerTargetGoal extends G super.start(); } @@ -1725,7 +1743,9 @@ index 61a7bb5f1760d47a13b6aafc123bcf3dff420860..15da909b4db3d7a7532aed550208ecbb + public void poll() { + if (!(this.mob.getGoalCtx().result() instanceof List toStop)) return; + for (var neutralMob : toStop) { -+ ((NeutralMob)neutralMob).forgetCurrentTargetAndRefreshUniversalAnger(); ++ if (EntitySelector.NO_SPECTATORS.test((net.minecraft.world.entity.Entity) neutralMob)) { ++ ((NeutralMob) neutralMob).forgetCurrentTargetAndRefreshUniversalAnger(); ++ } + } + } + // Leaf end - Async alert other @@ -1926,6 +1946,19 @@ index c84f63f064a7769761f75cdedaceacde858b9b4e..9b7d059bf68ff24dddedb16ec62a9b67 } static class TurtleMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables +diff --git a/net/minecraft/world/entity/animal/wolf/Wolf.java b/net/minecraft/world/entity/animal/wolf/Wolf.java +index 936ee9e80239ad965be75ceeb38d5248243e9c4e..cdb6781b319559ea23bad3b371a91d49cfc8adcc 100644 +--- a/net/minecraft/world/entity/animal/wolf/Wolf.java ++++ b/net/minecraft/world/entity/animal/wolf/Wolf.java +@@ -723,7 +723,7 @@ public class Wolf extends TamableAnimal implements NeutralMob { + + @Override + public boolean isFood(ItemStack stack) { +- return stack.is(ItemTags.WOLF_FOOD); ++ return stack.is(ItemTags.WOLF_FOOD); // Leaf - Async Target Finding - diff on change + } + + @Override 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 @@ -1953,6 +1986,45 @@ index 2e04078664cd723e3e0c80565e4b6e6416b13901..ed790a407b4be9e2f7fcb8cf36b8d03c } static class DrownedGoToWaterGoal extends Goal { +diff --git a/net/minecraft/world/entity/monster/EnderMan.java b/net/minecraft/world/entity/monster/EnderMan.java +index b9160f939894f784e8a733b0c00055b51aec694a..1baaf3dd103c1dd8a1c7d9457e5f878f5d31d04d 100644 +--- a/net/minecraft/world/entity/monster/EnderMan.java ++++ b/net/minecraft/world/entity/monster/EnderMan.java +@@ -590,10 +590,34 @@ public class EnderMan extends Monster implements NeutralMob { + + @Override + public boolean canUse() { ++ // Leaf start - Async Target Finding ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ if (poll()) { ++ return true; ++ } ++ final EnderMan enderman = this.enderman; ++ final var ctx = enderman.getGoalCtx(); ++ if (!ctx.state) return false; ++ final var level = getServerLevel(this.enderman); ++ final var cond = this.startAggroTargetConditions.range(this.getFollowDistance()).copy(); ++ ctx.wake = () -> level.getNearestPlayer(cond, enderman); ++ return false; ++ } ++ // Leaf end - Async Target Finding + this.pendingTarget = getServerLevel(this.enderman).getNearestPlayer(this.startAggroTargetConditions.range(this.getFollowDistance()), this.enderman); + return this.pendingTarget != null; + } + ++ // Leaf start - Async Target Finding ++ protected boolean poll() { ++ if (!(this.mob.getGoalCtx().result() instanceof Player player)) return false; ++ var serverLevel = getServerLevel(this.enderman); ++ if (!this.startAggroTargetConditions.range(this.getFollowDistance()).test(serverLevel, enderman, player)) return false; ++ this.pendingTarget = player; ++ return true; ++ } ++ // Leaf end - Async Target Finding ++ + @Override + public void start() { + this.aggroTime = this.adjustedTickDelay(5); 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 @@ -1975,7 +2047,7 @@ index e1717b5c854aa81fdd7b7e715d7c3498d9f86072..3627e7479b4deea28e268245410ec4cd 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 +index 20be5c4200bfee01d62fa1103a3663b91107d5d8..3f5fb9d0002f7e11cd6dbe38eaa054c63a625d9a 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 @@ -1994,7 +2066,7 @@ index 20be5c4200bfee01d62fa1103a3663b91107d5d8..5fbe3356ba9b7c4c09f87fda59971552 + // 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 ++ //List list = Lists.newArrayList(); // Leaf - Async target finding - unused + // Leaf end - Async target finding // Paper start - rewrite chunk system diff --git a/leaf-server/minecraft-patches/features/0235-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch b/leaf-server/minecraft-patches/features/0235-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch deleted file mode 100644 index 5352001f..00000000 --- a/leaf-server/minecraft-patches/features/0235-Replace-ConcurrentLong2ReferenceChainedHashTable-wit.patch +++ /dev/null @@ -1,110 +0,0 @@ -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/0237-Optimize-ThreadedTicketLevelPropagator.patch b/leaf-server/minecraft-patches/features/0236-Optimize-ThreadedTicketLevelPropagator.patch similarity index 98% rename from leaf-server/minecraft-patches/features/0237-Optimize-ThreadedTicketLevelPropagator.patch rename to leaf-server/minecraft-patches/features/0236-Optimize-ThreadedTicketLevelPropagator.patch index 9c5503b2..2b09ff0c 100644 --- a/leaf-server/minecraft-patches/features/0237-Optimize-ThreadedTicketLevelPropagator.patch +++ b/leaf-server/minecraft-patches/features/0236-Optimize-ThreadedTicketLevelPropagator.patch @@ -5,7 +5,7 @@ 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 +index 310a8f80debadd64c2d962ebf83b7d0505ce6e42..878f8beb769e87f1de70e7be963e88678a89dd0a 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 { diff --git a/leaf-server/minecraft-patches/features/0238-Optimise-MobEffectUtil-getDigSpeedAmplification.patch b/leaf-server/minecraft-patches/features/0237-Optimise-MobEffectUtil-getDigSpeedAmplification.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0238-Optimise-MobEffectUtil-getDigSpeedAmplification.patch rename to leaf-server/minecraft-patches/features/0237-Optimise-MobEffectUtil-getDigSpeedAmplification.patch diff --git a/leaf-server/minecraft-patches/features/0239-Optimise-chunkUnloads.patch b/leaf-server/minecraft-patches/features/0238-Optimise-chunkUnloads.patch similarity index 99% rename from leaf-server/minecraft-patches/features/0239-Optimise-chunkUnloads.patch rename to leaf-server/minecraft-patches/features/0238-Optimise-chunkUnloads.patch index 891f438b..8534a264 100644 --- a/leaf-server/minecraft-patches/features/0239-Optimise-chunkUnloads.patch +++ b/leaf-server/minecraft-patches/features/0238-Optimise-chunkUnloads.patch @@ -159,7 +159,7 @@ index 4ca68a903e67606fc4ef0bfa9862a73797121c8b..bed3a64388bb43e47c2ba4e67f7dde5b public static final class SaveState { diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java -index 36c033b0ee63dfc273d721fb4b614733e8fdef19..dc9f1bc6dd8b1057da3416e24f15f2329658f996 100644 +index 1cc33a038060aaf5258ee4f1deb19b4a1be59a29..a14247b043715de50b253ab1f10a9244003f8797 100644 --- a/net/minecraft/world/level/chunk/LevelChunkSection.java +++ b/net/minecraft/world/level/chunk/LevelChunkSection.java @@ -24,6 +24,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ diff --git a/leaf-server/minecraft-patches/features/0240-Optimize-BlockEntityType-isValid.patch b/leaf-server/minecraft-patches/features/0239-Optimize-BlockEntityType-isValid.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0240-Optimize-BlockEntityType-isValid.patch rename to leaf-server/minecraft-patches/features/0239-Optimize-BlockEntityType-isValid.patch 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/0240-PaperPR-Add-ticket-on-player-join-to-avoid-chunk-loa.patch similarity index 97% rename from leaf-server/minecraft-patches/features/0241-PaperPR-Add-ticket-on-player-join-to-avoid-chunk-loa.patch rename to leaf-server/minecraft-patches/features/0240-PaperPR-Add-ticket-on-player-join-to-avoid-chunk-loa.patch index de48f0f8..c68a860e 100644 --- a/leaf-server/minecraft-patches/features/0241-PaperPR-Add-ticket-on-player-join-to-avoid-chunk-loa.patch +++ b/leaf-server/minecraft-patches/features/0240-PaperPR-Add-ticket-on-player-join-to-avoid-chunk-loa.patch @@ -15,7 +15,7 @@ The delay is currently set to 2 seconds, however, we may want to adjust this bef 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 +index 3a78e7512772fd3f7cf8f221e3a72474def14bea..ba52af914e9e231caa0ac50562e9a6925908c615 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 { diff --git a/leaf-server/minecraft-patches/features/0243-Sakura-copy-EntityList-implementation-to-BasicEntity.patch b/leaf-server/minecraft-patches/features/0241-Sakura-copy-EntityList-implementation-to-BasicEntity.patch similarity index 90% rename from leaf-server/minecraft-patches/features/0243-Sakura-copy-EntityList-implementation-to-BasicEntity.patch rename to leaf-server/minecraft-patches/features/0241-Sakura-copy-EntityList-implementation-to-BasicEntity.patch index acd34d0c..9f2b5d09 100644 --- a/leaf-server/minecraft-patches/features/0243-Sakura-copy-EntityList-implementation-to-BasicEntity.patch +++ b/leaf-server/minecraft-patches/features/0241-Sakura-copy-EntityList-implementation-to-BasicEntity.patch @@ -5,10 +5,10 @@ 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 +index 956d48fb7146b9eb2a5b5b4e23a83f60d0e40b4c..5847cbe6229f217d0361eda0950668bd0e9573f4 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 { +@@ -415,6 +415,13 @@ public final class ChunkEntitySlices { private E[] storage; private int size; @@ -22,7 +22,7 @@ index 7b686d834e4eb36be5758b0e0a846a70d1e2294b..37930d1b87378ac3e8c7f5ebd79148bb public BasicEntityList() { this(0); -@@ -402,6 +409,7 @@ public final class ChunkEntitySlices { +@@ -435,6 +442,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]; @@ -30,7 +30,7 @@ index 7b686d834e4eb36be5758b0e0a846a70d1e2294b..37930d1b87378ac3e8c7f5ebd79148bb } else { this.storage = Arrays.copyOf(this.storage, this.storage.length * 2); } -@@ -415,6 +423,7 @@ public final class ChunkEntitySlices { +@@ -448,6 +456,7 @@ public final class ChunkEntitySlices { } else { this.storage[idx] = entity; } @@ -38,7 +38,7 @@ index 7b686d834e4eb36be5758b0e0a846a70d1e2294b..37930d1b87378ac3e8c7f5ebd79148bb } public int indexOf(final E entity) { -@@ -430,24 +439,32 @@ public final class ChunkEntitySlices { +@@ -463,24 +472,32 @@ public final class ChunkEntitySlices { } public boolean remove(final E entity) { diff --git a/leaf-server/minecraft-patches/features/0242-Protocol-Core.patch b/leaf-server/minecraft-patches/features/0242-Protocol-Core.patch new file mode 100644 index 00000000..87fd99dd --- /dev/null +++ b/leaf-server/minecraft-patches/features/0242-Protocol-Core.patch @@ -0,0 +1,85 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Tue, 6 May 2025 17:44:16 +0900 +Subject: [PATCH] Protocol Core + + +diff --git a/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java b/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java +index fb263fa1f30a7dfcb7ec2656abfb38e5fe88eac9..a5f4deff8ecde365b7fbec92760b1a7cc5b8c58e 100644 +--- a/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java ++++ b/net/minecraft/network/protocol/common/custom/CustomPacketPayload.java +@@ -40,6 +40,12 @@ public interface CustomPacketPayload { + + @Override + public void encode(B buffer, CustomPacketPayload value) { ++ // Leaf start - Protocol core ++ if (value instanceof org.dreeam.leaf.protocol.LeafCustomPayload payload) { ++ org.dreeam.leaf.protocol.Protocols.write(buffer, payload); ++ return; ++ } ++ // Leaf end - Protocol core + this.writeCap(buffer, value.type(), value); + } + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 78aee57ad8224b0728411c699d2e3844847c9c79..8f5b400bdf5c1c194f75ee98e2f1e984e6137a50 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1770,6 +1770,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop mountedOrDismounted(List entities) { +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index ff4ca34dca8e0061977ea63707eb34c6e06dd66a..30783379baee569ec7ff51d34524ccdcba38d764 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -809,6 +809,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + } + } + // Purpur end - Ridables ++ org.dreeam.leaf.protocol.Protocols.tickPlayer(this); // Leaf - Protocol core + } + + private void updatePlayerAttributes() { +diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +index 434be9d08d8e816e2dea1e9d23fa26d21b9f35f6..98f169c64b31ff523fe8b2d435f796a5156203a9 100644 +--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -162,6 +162,8 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + ++ org.dreeam.leaf.protocol.Protocols.handle(this.player, discardedPayload); // Leaf - Protocol core ++ + final net.minecraft.resources.ResourceLocation identifier = packet.payload().type().id(); + final byte[] data = discardedPayload.data(); + try { +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index 8173242d149709f092e6d609f6e1d831eca0a884..05bc165ecae4151355dd9329673f10cff6253ca4 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -536,6 +536,7 @@ public abstract class PlayerList { + return this.remove(player, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? player.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(player.getDisplayName()))); + } + public @Nullable net.kyori.adventure.text.Component remove(ServerPlayer player, net.kyori.adventure.text.Component leaveMessage) { ++ org.dreeam.leaf.protocol.Protocols.disconnected(player); // Leaf - Protocol core + // Paper end - Fix kick event leave message not being sent + org.purpurmc.purpur.task.BossBarTask.removeFromAll(player.getBukkitEntity()); // Purpur - Implement TPSBar + net.minecraft.server.network.ServerGamePacketListenerImpl.afkCooldown.remove(player.getBukkitEntity().getUniqueId()); // Leaf - Improve Purpur AFK system diff --git a/leaf-server/minecraft-patches/features/0243-Reduce-PlayerChunk-Updates.patch b/leaf-server/minecraft-patches/features/0243-Reduce-PlayerChunk-Updates.patch new file mode 100644 index 00000000..08e168c2 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0243-Reduce-PlayerChunk-Updates.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Thu, 8 May 2025 10:08:54 +0200 +Subject: [PATCH] Reduce PlayerChunk Updates + + +diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index a3069b29a1b78012314747d705e27c167acd10b3..3b322c7d65a7f71b94cff660d8702abf646f52e6 100644 +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -1418,6 +1418,8 @@ public class ServerGamePacketListenerImpl + this.resetPosition(); + } + ++ final net.minecraft.world.level.ChunkPos playerStartChunkPosition = this.player.chunkPosition(); // Leaf - Reduce PlayerChunk Updates ++ + if (this.player.hasClientLoaded()) { + float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); final float toYaw = f; // Paper - OBFHELPER + float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); final float toPitch = f1; // Paper - OBFHELPER +@@ -1696,7 +1698,7 @@ public class ServerGamePacketListenerImpl + && !isFallFlying + && !isAutoSpinAttack + && this.noBlocksAround(this.player); +- this.player.serverLevel().getChunkSource().move(this.player); ++ if (!org.dreeam.leaf.config.modules.opt.ReduceChunkSourceUpdates.enabled || this.player.serverLevel() != serverLevel || this.player.chunkPosition() == playerStartChunkPosition) this.player.serverLevel().getChunkSource().move(this.player); // Leaf - Reduce PlayerChunk Updates + Vec3 vec3 = new Vec3(this.player.getX() - x, this.player.getY() - y, this.player.getZ() - z); + this.player.setOnGroundWithMovement(packet.isOnGround(), packet.horizontalCollision(), vec3); + this.player.doCheckFallDamage(vec3.x, vec3.y, vec3.z, packet.isOnGround()); diff --git a/leaf-server/minecraft-patches/features/0244-Async-switch-connection-state.patch b/leaf-server/minecraft-patches/features/0244-Async-switch-connection-state.patch new file mode 100644 index 00000000..8fe1a4fa --- /dev/null +++ b/leaf-server/minecraft-patches/features/0244-Async-switch-connection-state.patch @@ -0,0 +1,156 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Fri, 9 May 2025 16:55:34 +0900 +Subject: [PATCH] Async switch connection state + + +diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java +index f3e9de8716f5e1a72ec465ee897c8f0413f7b1c3..c83ee2137a57e62003b1d20c3ceea9f569350a53 100644 +--- a/net/minecraft/network/Connection.java ++++ b/net/minecraft/network/Connection.java +@@ -342,6 +342,11 @@ public class Connection extends SimpleChannelInboundHandler> { + if (protocolInfo.flow() != this.getReceiving()) { + throw new IllegalStateException("Invalid inbound protocol: " + protocolInfo.id()); + } else { ++ // Leaf start - Async switch connection state ++ if (org.dreeam.leaf.config.modules.network.AlternativeJoin.enabled && ca.spottedleaf.moonrise.common.util.TickThread.isTickThread()) { ++ this.channel.config().setAutoRead(false); ++ } ++ // Leaf end - Async switch connection state + this.packetListener = packetInfo; + this.disconnectListener = null; + UnconfiguredPipelineHandler.InboundConfigurationTask inboundConfigurationTask = UnconfiguredPipelineHandler.setupInboundProtocol(protocolInfo); +@@ -351,7 +356,14 @@ public class Connection extends SimpleChannelInboundHandler> { + inboundConfigurationTask = inboundConfigurationTask.andThen(context -> context.pipeline().addAfter("decoder", "bundler", packetBundlePacker)); + } + +- syncAfterConfigurationChange(this.channel.writeAndFlush(inboundConfigurationTask)); ++ // Leaf start - Async switch connection state ++ var cf = this.channel.writeAndFlush(inboundConfigurationTask); ++ if (org.dreeam.leaf.config.modules.network.AlternativeJoin.enabled && ca.spottedleaf.moonrise.common.util.TickThread.isTickThread()) { ++ cf.addListener((ChannelFutureListener) Connection::syncAfterConfigurationChange); ++ return; ++ } ++ syncAfterConfigurationChange(cf); ++ // Leaf end - Async switch connection state + } + } + +@@ -369,7 +381,38 @@ public class Connection extends SimpleChannelInboundHandler> { + } + + boolean flag = protocolInfo.id() == ConnectionProtocol.LOGIN; +- syncAfterConfigurationChange(this.channel.writeAndFlush(outboundConfigurationTask.andThen(context -> this.sendLoginDisconnect = flag))); ++ var cf = this.channel.writeAndFlush(outboundConfigurationTask.andThen(context -> this.sendLoginDisconnect = flag)); ++ // Leaf start - Async switch connection state ++ if (org.dreeam.leaf.config.modules.network.AlternativeJoin.enabled) { ++ if (ca.spottedleaf.moonrise.common.util.TickThread.isTickThread()) { ++ throw new IllegalStateException("Thread failed netty thread check: Switching outbound protocol state use setupOutboundProtocolAsync instead"); ++ } ++ } ++ syncAfterConfigurationChange(cf); ++ } ++ } ++ ++ public @Nullable ChannelFuture setupOutboundProtocolAsync(ProtocolInfo protocolInfo) { ++ if (protocolInfo.flow() != this.getSending()) { ++ throw new IllegalStateException("Invalid outbound protocol: " + protocolInfo.id()); ++ } else { ++ UnconfiguredPipelineHandler.OutboundConfigurationTask outboundConfigurationTask = UnconfiguredPipelineHandler.setupOutboundProtocol(protocolInfo); ++ BundlerInfo bundlerInfo = protocolInfo.bundlerInfo(); ++ if (bundlerInfo != null) { ++ PacketBundleUnpacker packetBundleUnpacker = new PacketBundleUnpacker(bundlerInfo); ++ outboundConfigurationTask = outboundConfigurationTask.andThen( ++ context -> context.pipeline().addAfter("encoder", "unbundler", packetBundleUnpacker) ++ ); ++ } ++ ++ boolean flag = protocolInfo.id() == ConnectionProtocol.LOGIN; ++ var cf = this.channel.writeAndFlush(outboundConfigurationTask.andThen(context -> this.sendLoginDisconnect = flag)); ++ if (org.dreeam.leaf.config.modules.network.AlternativeJoin.enabled) { ++ cf.addListener((ChannelFutureListener) Connection::syncAfterConfigurationChange); ++ return cf; ++ } ++ return null; ++ // Leaf end - Async switch connection state + } + } + +diff --git a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java +index 2e9eb04c7c4342393c05339906c267bca9ff29b1..53b9daa909c2b89046d5af515e17afe09ea7015a 100644 +--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java +@@ -140,11 +140,34 @@ public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketLis + } + } + ++ private volatile boolean changingState = false; // Leaf - Async switch connection state + @Override + public void handleConfigurationFinished(ServerboundFinishConfigurationPacket packet) { ++ // Leaf start - Async switch connection state ++ if (org.dreeam.leaf.config.modules.network.AlternativeJoin.enabled && !changingState) { ++ changingState = true; ++ this.finishCurrentTask(JoinWorldTask.TYPE); ++ this.connection.setupOutboundProtocolAsync(GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()))).addListener(l -> { ++ try { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.server); ++ } catch (net.minecraft.server.RunningOnDifferentThreadException ignored) { ++ } catch ( ++ io.papermc.paper.util.ServerStopRejectedExecutionException ignored) { // Paper - do not prematurely disconnect players on stop ++ } catch (java.util.concurrent.RejectedExecutionException var6) { ++ this.connection.disconnect(Component.translatable("multiplayer.disconnect.server_shutdown")); ++ } catch (ClassCastException var7) { ++ LOGGER.error("Received {} that couldn't be processed", packet.getClass(), var7); ++ this.connection.disconnect(Component.translatable("multiplayer.disconnect.invalid_packet")); ++ } ++ }); ++ return; ++ } + PacketUtils.ensureRunningOnSameThread(packet, this, this.server); +- this.finishCurrentTask(JoinWorldTask.TYPE); +- this.connection.setupOutboundProtocol(GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()))); ++ if (!org.dreeam.leaf.config.modules.network.AlternativeJoin.enabled) { ++ this.finishCurrentTask(JoinWorldTask.TYPE); ++ this.connection.setupOutboundProtocol(GameProtocols.CLIENTBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()))); ++ } ++ // Leaf end - Async switch connection state + + try { + PlayerList playerList = this.server.getPlayerList(); +diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 405b62c082017024abae7ccc1db5f74caab1eabf..1ad1fc46775a473e6f7fd97eac4b8c7110c7332b 100644 +--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -471,11 +471,31 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); + } + ++ private volatile boolean changingState = false; // Leaf - Async switch connection state + @Override + public void handleLoginAcknowledgement(ServerboundLoginAcknowledgedPacket packet) { ++ // Leaf start - Async switch connection state ++ if (org.dreeam.leaf.config.modules.network.AlternativeJoin.enabled && !changingState) { ++ changingState = true; ++ this.connection.setupOutboundProtocolAsync(ConfigurationProtocols.CLIENTBOUND).addListener(l -> { ++ try { ++ net.minecraft.network.protocol.PacketUtils.ensureRunningOnSameThread(packet, this, this.server); ++ } catch (net.minecraft.server.RunningOnDifferentThreadException ignored) { ++ } catch ( ++ io.papermc.paper.util.ServerStopRejectedExecutionException ignored) { // Paper - do not prematurely disconnect players on stop ++ } catch (java.util.concurrent.RejectedExecutionException var6) { ++ this.connection.disconnect(Component.translatable("multiplayer.disconnect.server_shutdown")); ++ } catch (ClassCastException var7) { ++ LOGGER.error("Received {} that couldn't be processed", packet.getClass(), var7); ++ this.connection.disconnect(Component.translatable("multiplayer.disconnect.invalid_packet")); ++ } ++ }); ++ return; ++ } ++ // Leaf end - Async switch connection state + net.minecraft.network.protocol.PacketUtils.ensureRunningOnSameThread(packet, this, this.server); // CraftBukkit + Validate.validState(this.state == ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet"); +- this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); ++ if (!org.dreeam.leaf.config.modules.network.AlternativeJoin.enabled) this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); // Leaf - Async switch connection state + CommonListenerCookie commonListenerCookie = CommonListenerCookie.createInitial(Objects.requireNonNull(this.authenticatedProfile), this.transferred); + ServerConfigurationPacketListenerImpl serverConfigurationPacketListenerImpl = new ServerConfigurationPacketListenerImpl( + this.server, this.connection, commonListenerCookie, this.player // CraftBukkit diff --git a/leaf-server/minecraft-patches/features/0245-Optimise-BlockEntities-tickersInLevel.patch b/leaf-server/minecraft-patches/features/0245-Optimise-BlockEntities-tickersInLevel.patch new file mode 100644 index 00000000..8dbf75c3 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0245-Optimise-BlockEntities-tickersInLevel.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Thu, 8 May 2025 13:30:07 +0200 +Subject: [PATCH] Optimise BlockEntities tickersInLevel + + +diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java +index 5ca0aa1fdaae3ba03a464e2c5bc061adc076b649..3e00ef33413764df933c85bb836d2cd327248edd 100644 +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java +@@ -73,7 +73,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + return ""; + } + }; +- private final Map tickersInLevel = Maps.newHashMap(); ++ private final Map tickersInLevel = org.dreeam.leaf.config.modules.opt.OptimiseBlockEntities.enabled ? new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>() : Maps.newHashMap(); // Leaf - Optimise BlockEntities tickersInLevel + public boolean loaded; + public final ServerLevel level; // CraftBukkit - type + @Nullable diff --git a/leaf-server/minecraft-patches/features/0246-Flush-location-while-knockback.patch b/leaf-server/minecraft-patches/features/0246-Flush-location-while-knockback.patch new file mode 100644 index 00000000..ca560e24 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0246-Flush-location-while-knockback.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sun, 11 May 2025 19:45:58 +0200 +Subject: [PATCH] Flush location while knockback + + +diff --git a/net/minecraft/world/entity/player/Player.java b/net/minecraft/world/entity/player/Player.java +index 4217131b5f7aa985d5af452554849847a36ce9ce..d1b7fffc20ccd00bcc82f830a1f53a85fd86658a 100644 +--- a/net/minecraft/world/entity/player/Player.java ++++ b/net/minecraft/world/entity/player/Player.java +@@ -1352,6 +1352,13 @@ public abstract class Player extends LivingEntity { + } + + if (!cancelled) { ++ // Leaf start - Flush location while knockback ++ if (org.dreeam.leaf.config.modules.gameplay.Knockback.flushKnockback && target instanceof ServerPlayer targetPlayer && this instanceof ServerPlayer player1) { ++ targetPlayer.connection.send(net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket.of(this)); ++ player1.connection.send(new net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket(targetPlayer)); ++ player1.connection.send(net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket.of(targetPlayer)); ++ } ++ // Leaf end - Flush location while knockback + ((ServerPlayer)target).connection.send(new ClientboundSetEntityMotionPacket(target)); + target.hurtMarked = false; + target.setDeltaMovement(deltaMovement); +@@ -1420,6 +1427,12 @@ public abstract class Player extends LivingEntity { + } + + this.causeFoodExhaustion(this.level().spigotConfig.combatExhaustion, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.ATTACK); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value ++ // Leaf start - Flush location while knockback ++ if (org.dreeam.leaf.config.modules.gameplay.Knockback.flushKnockback && this instanceof ServerPlayer player1 && target instanceof ServerPlayer target1) { ++ target1.connection.connection.flushChannel(); ++ player1.connection.connection.flushChannel(); ++ } ++ // Leaf end - Flush location while knockback + } else { + sendSoundEffect(this, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F); // Paper - send while respecting visibility + this.containerMenu.sendAllDataToRemote(); // CraftBukkit - resync on cancelled event diff --git a/leaf-server/minecraft-patches/features/0247-Only-tick-items-at-hand.patch b/leaf-server/minecraft-patches/features/0247-Only-tick-items-at-hand.patch new file mode 100644 index 00000000..edecdadd --- /dev/null +++ b/leaf-server/minecraft-patches/features/0247-Only-tick-items-at-hand.patch @@ -0,0 +1,49 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Fri, 9 May 2025 23:50:55 +0200 +Subject: [PATCH] Only tick items at hand + + +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index 30783379baee569ec7ff51d34524ccdcba38d764..e2178e12a5c41ab525eea2cd13f14aadd8d66f49 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -838,12 +838,19 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + super.tick(); + } + ++ // Leaf start - Only tick items at hand ++ if (org.dreeam.leaf.config.modules.opt.OptimizeItemTicking.onlyTickItemsInHand) { ++ this.synchronizeSpecialItemUpdates(this.getMainHandItem()); ++ this.synchronizeSpecialItemUpdates(this.getOffhandItem()); ++ } else { + for (int i = 0; i < this.getInventory().getContainerSize(); i++) { + ItemStack item = this.getInventory().getItem(i); + if (!item.isEmpty()) { + this.synchronizeSpecialItemUpdates(item); + } + } ++ } ++ // Leaf end - Only tick items at hand + + if (this.getHealth() != this.lastSentHealth + || this.lastSentFood != this.foodData.getFoodLevel() +diff --git a/net/minecraft/world/entity/player/Player.java b/net/minecraft/world/entity/player/Player.java +index d1b7fffc20ccd00bcc82f830a1f53a85fd86658a..688a63644a66c0cd3f08deb3786d756c310c079c 100644 +--- a/net/minecraft/world/entity/player/Player.java ++++ b/net/minecraft/world/entity/player/Player.java +@@ -639,7 +639,14 @@ public abstract class Player extends LivingEntity { + } + + this.tickRegeneration(); ++ // Leaf start - Only tick items at hand ++ if (org.dreeam.leaf.config.modules.opt.OptimizeItemTicking.onlyTickItemsInHand) { ++ this.getMainHandItem().inventoryTick(this.level(), this, EquipmentSlot.MAINHAND); ++ this.getOffhandItem().inventoryTick(this.level(), this, EquipmentSlot.OFFHAND); ++ } else { + this.inventory.tick(); ++ } ++ // Leaf end - Only tick items at hand + this.oBob = this.bob; + if (this.abilities.flying && !this.isPassenger()) { + this.resetFallDistance(); diff --git a/leaf-server/minecraft-patches/features/0248-Smart-sort-items-in-NearestItemSensor.patch b/leaf-server/minecraft-patches/features/0248-Smart-sort-items-in-NearestItemSensor.patch new file mode 100644 index 00000000..0907f553 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0248-Smart-sort-items-in-NearestItemSensor.patch @@ -0,0 +1,36 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sat, 10 May 2025 22:04:42 +0200 +Subject: [PATCH] Smart sort items in NearestItemSensor + + +diff --git a/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java b/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java +index 09fd13e2d958da8326276c4dadf25bf488aff5ac..651797720f7fc6ff9dc614f4de56053c304b1170 100644 +--- a/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java +@@ -16,6 +16,12 @@ public class NearestItemSensor extends Sensor { + private static final long Y_RANGE = 16L; + public static final int MAX_DISTANCE_TO_WANTED_ITEM = 32; + ++ // Leaf start - Smart sort items in NearestItemSensor ++ private final org.dreeam.leaf.util.FastBitRadixSort itemSorter; ++ public NearestItemSensor() { ++ this.itemSorter = new org.dreeam.leaf.util.FastBitRadixSort(); ++ } ++ // Leaf end - Smart sort items in NearestItemSensor + @Override + public Set> requires() { + return ImmutableSet.of(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM); +@@ -25,10 +31,10 @@ public class NearestItemSensor extends Sensor { + protected void doTick(ServerLevel level, Mob entity) { + Brain brain = entity.getBrain(); + List entitiesOfClass = level.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox().inflate(32.0, 16.0, 32.0), itemEntity -> itemEntity.closerThan(entity, MAX_DISTANCE_TO_WANTED_ITEM) && entity.wantsToPickUp(level, itemEntity.getItem())); // Paper - Perf: Move predicate into getEntities +- entitiesOfClass.sort(Comparator.comparingDouble(entity::distanceToSqr)); ++ ItemEntity[] sortedItems = this.itemSorter.sort(entitiesOfClass, entity, ItemEntity.class); // Leaf - Smart sort items in NearestItemSensor + // Paper start - Perf: remove streams from hot code + ItemEntity nearest = null; +- for (final ItemEntity itemEntity : entitiesOfClass) { ++ for (final ItemEntity itemEntity : sortedItems) { // Leaf - Smart sort items in NearestItemSensor + if (entity.hasLineOfSight(itemEntity)) { // Paper - Perf: Move predicate into getEntities + nearest = itemEntity; + break; diff --git a/leaf-server/minecraft-patches/features/0249-Optimise-player-movement-checks.patch b/leaf-server/minecraft-patches/features/0249-Optimise-player-movement-checks.patch new file mode 100644 index 00000000..5a9c6c25 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0249-Optimise-player-movement-checks.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sat, 10 May 2025 23:18:17 +0200 +Subject: [PATCH] Optimise player movement checks + + +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index 658b8af9b1db252d5664956907a5c0db538018b0..6f3fd83c9f0bafe613c780ba56430084e91fdd2a 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -1190,7 +1190,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + // Paper end + +- movement = this.maybeBackOffFromEdge(movement, type); ++ if (!org.dreeam.leaf.config.modules.opt.OptimizePlayerMovementProcessing.enabled) movement = this.maybeBackOffFromEdge(movement, type); // Leaf - Optimise player movement checks + Vec3 vec3 = this.collide(movement); + double d = vec3.lengthSqr(); + if (d > 1.0E-7 || movement.lengthSqr() - d < 1.0E-7) { diff --git a/leaf-server/minecraft-patches/features/0250-Remove-streams-in-MobSensor.patch b/leaf-server/minecraft-patches/features/0250-Remove-streams-in-MobSensor.patch new file mode 100644 index 00000000..3c2dcbd5 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0250-Remove-streams-in-MobSensor.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Mon, 12 May 2025 19:11:16 +0200 +Subject: [PATCH] Remove streams in MobSensor + + +diff --git a/net/minecraft/world/entity/ai/sensing/MobSensor.java b/net/minecraft/world/entity/ai/sensing/MobSensor.java +index bda210b4809a5aade7ab4d0f26fdda4d5f53f619..2271196768bfc90626e007a70602f818c832e348 100644 +--- a/net/minecraft/world/entity/ai/sensing/MobSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/MobSensor.java +@@ -40,10 +40,15 @@ public class MobSensor extends Sensor { + public void checkForMobsNearby(T sensingEntity) { + Optional> memory = sensingEntity.getBrain().getMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES); + if (!memory.isEmpty()) { +- boolean flag = memory.get().stream().anyMatch(livingEntity -> this.mobTest.test(sensingEntity, livingEntity)); +- if (flag) { +- this.mobDetected(sensingEntity); ++ // Leaf start - Remove streams in MobSensor ++ List entities = memory.get(); ++ for (LivingEntity livingEntity : entities) { ++ if (this.mobTest.test(sensingEntity, livingEntity)) { ++ this.mobDetected(sensingEntity); ++ break; ++ } + } ++ // Leaf end - Remove streams in MobSensor + } + } + diff --git a/leaf-server/minecraft-patches/features/0251-Remove-streams-in-TemptingSensor.patch b/leaf-server/minecraft-patches/features/0251-Remove-streams-in-TemptingSensor.patch new file mode 100644 index 00000000..4a2c563f --- /dev/null +++ b/leaf-server/minecraft-patches/features/0251-Remove-streams-in-TemptingSensor.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Mon, 12 May 2025 19:39:03 +0200 +Subject: [PATCH] Remove streams in TemptingSensor + + +diff --git a/net/minecraft/world/entity/ai/sensing/TemptingSensor.java b/net/minecraft/world/entity/ai/sensing/TemptingSensor.java +index 6074c091d0df7843c9f9dc691703eba1a24a7253..5919192968d7272bbf119f248def7e96a1ea359b 100644 +--- a/net/minecraft/world/entity/ai/sensing/TemptingSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/TemptingSensor.java +@@ -28,15 +28,21 @@ public class TemptingSensor extends Sensor { + protected void doTick(ServerLevel level, PathfinderMob entity) { + Brain brain = entity.getBrain(); + TargetingConditions targetingConditions = TEMPT_TARGETING.copy().range((float)entity.getAttributeValue(Attributes.TEMPT_RANGE)); +- List list = level.players() +- .stream() +- .filter(EntitySelector.NO_SPECTATORS) +- .filter(serverPlayer -> targetingConditions.test(level, entity, serverPlayer)) +- .filter(this::playerHoldingTemptation) +- .filter(serverPlayer -> !entity.hasPassenger(serverPlayer)) +- .sorted(Comparator.comparingDouble(entity::distanceToSqr)) +- .collect(Collectors.toList()); ++ // Leaf start - Remove streams in TemptingSensor ++ List allPlayers = level.players(); ++ List list = new java.util.ArrayList<>(); ++ for (Player serverPlayer : allPlayers) { ++ if (EntitySelector.NO_SPECTATORS.test(serverPlayer) && ++ targetingConditions.test(level, entity, serverPlayer) && ++ this.playerHoldingTemptation(serverPlayer) && ++ !entity.hasPassenger(serverPlayer)) { ++ list.add(serverPlayer); ++ } ++ } ++ // Leaf end - Remove streams in TemptingSensor ++ + if (!list.isEmpty()) { ++ list.sort(Comparator.comparingDouble(entity::distanceToSqr)); // Leaf - Remove streams in TemptingSensor + Player player = list.get(0); + // CraftBukkit start + org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent( diff --git a/leaf-server/minecraft-patches/features/0252-Use-HashedList-on-WeightedList.patch b/leaf-server/minecraft-patches/features/0252-Use-HashedList-on-WeightedList.patch new file mode 100644 index 00000000..c0f86827 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0252-Use-HashedList-on-WeightedList.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Tue, 13 May 2025 20:41:07 +0200 +Subject: [PATCH] Use HashedList on WeightedList + + +diff --git a/net/minecraft/util/random/WeightedList.java b/net/minecraft/util/random/WeightedList.java +index daab145028c59c07049a2cf75c4f159e4c5073dc..4bb07ffde3674dc8d09b858ec0bd1ebc8a83bff1 100644 +--- a/net/minecraft/util/random/WeightedList.java ++++ b/net/minecraft/util/random/WeightedList.java +@@ -19,6 +19,7 @@ public class WeightedList { // Paper - non-final + private final List> items; + @Nullable + private final WeightedList.Selector selector; ++ private List> entryHashList; // Leaf - Use HashedList on WeightedList + + protected WeightedList(List> items) { // Paper - protected + this.items = List.copyOf(items); +@@ -30,6 +31,7 @@ public class WeightedList { // Paper - non-final + } else { + this.selector = new WeightedList.Compact<>(this.items); + } ++ this.entryHashList = this.items.size() > 4 ? this.items : java.util.Collections.unmodifiableList(new org.dreeam.leaf.util.list.HashedReferenceList<>(this.items)); // Leaf - Use HashedList on WeightedList + } + + public static WeightedList of() { +@@ -80,7 +82,7 @@ public class WeightedList { // Paper - non-final + } + + public List> unwrap() { +- return this.items; ++ return this.entryHashList; // Leaf - Use HashedList on WeightedList + } + + public static Codec> codec(Codec elementCodec) { diff --git a/leaf-server/minecraft-patches/features/0253-Add-configurable-death-item-drop-knockback-settings.patch b/leaf-server/minecraft-patches/features/0253-Add-configurable-death-item-drop-knockback-settings.patch new file mode 100644 index 00000000..0416a24b --- /dev/null +++ b/leaf-server/minecraft-patches/features/0253-Add-configurable-death-item-drop-knockback-settings.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: adabugra <57899270+adabugra@users.noreply.github.com> +Date: Thu, 15 May 2025 19:09:04 +0300 +Subject: [PATCH] Add configurable death item drop knockback settings + + +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index e2178e12a5c41ab525eea2cd13f14aadd8d66f49..8cc4ee6b62a7f7bd86dce0b5cd7c470156872fdc 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -1033,7 +1033,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + if (!keepInventory) { + for (ItemStack item : this.getInventory().getContents()) { + if (!item.isEmpty() && !EnchantmentHelper.has(item, net.minecraft.world.item.enchantment.EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) { +- loot.add(new DefaultDrop(item, stack -> this.drop(stack, true, false, false, null))); // Paper - Restore vanilla drops behavior; drop function taken from Inventory#dropAll (don't fire drop event) ++ loot.add(new DefaultDrop(item, stack -> this.drop(stack, org.dreeam.leaf.config.modules.gameplay.DeathItemDropKnockback.dropAround, false, false, null))); // Paper - Restore vanilla drops behavior; drop function taken from Inventory#dropAll (don't fire drop event) // Leaf - Add configurable death item drop knockback settings + } + } + } +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java +index 8461be7c6723925d26f7f162564a18f4aa162089..27dffa0a104b27687fd7ec0add46657bcab4299a 100644 +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -4088,9 +4088,9 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + if (randomizeMotion) { +- float f = this.random.nextFloat() * 0.5F; ++ float f = this.random.nextFloat() * (float) org.dreeam.leaf.config.modules.gameplay.DeathItemDropKnockback.horizontalForce; // Leaf - Add configurable death item drop knockback settings + float f1 = this.random.nextFloat() * (float) (Math.PI * 2); +- itemEntity.setDeltaMovement(-Mth.sin(f1) * f, 0.2F, Mth.cos(f1) * f); ++ itemEntity.setDeltaMovement(-Mth.sin(f1) * f, (float) org.dreeam.leaf.config.modules.gameplay.DeathItemDropKnockback.verticalForce, Mth.cos(f1) * f); // Leaf - Add configurable death item drop knockback settings + } else { + float f = 0.3F; + float f1 = Mth.sin(this.getXRot() * (float) (Math.PI / 180.0)); diff --git a/leaf-server/minecraft-patches/features/0254-Optimize-AttributeMap.patch b/leaf-server/minecraft-patches/features/0254-Optimize-AttributeMap.patch new file mode 100644 index 00000000..4bce8f29 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0254-Optimize-AttributeMap.patch @@ -0,0 +1,86 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Thu, 15 May 2025 21:11:18 +0900 +Subject: [PATCH] Optimize AttributeMap + + +diff --git a/net/minecraft/world/entity/ai/attributes/Attribute.java b/net/minecraft/world/entity/ai/attributes/Attribute.java +index f8419dde44ebc7324e783f8bee42132d5ec973c3..406767c60ec1a324faaf5d3658b161647497f99b 100644 +--- a/net/minecraft/world/entity/ai/attributes/Attribute.java ++++ b/net/minecraft/world/entity/ai/attributes/Attribute.java +@@ -16,10 +16,15 @@ public class Attribute { + private boolean syncable; + private final String descriptionId; + private Attribute.Sentiment sentiment = Attribute.Sentiment.POSITIVE; ++ // Leaf start - Optimize AttributeMap ++ public final int uid; ++ private static final java.util.concurrent.atomic.AtomicInteger SIZE = new java.util.concurrent.atomic.AtomicInteger(); ++ // Leaf end - Optimize AttributeMap + + protected Attribute(String descriptionId, double defaultValue) { + this.defaultValue = defaultValue; + this.descriptionId = descriptionId; ++ this.uid = SIZE.getAndAdd(1); // Leaf - Optimize AttributeMap + } + + public double getDefaultValue() { +diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java +index 701025715e0aca3c1f920a66f9b3d03ec08eaf02..1ef934b02bd99c0fe997e9ec163457b39dba55f0 100644 +--- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java +@@ -15,12 +15,12 @@ import net.minecraft.resources.ResourceLocation; + + public class AttributeMap { + // Gale start - Lithium - replace AI attributes with optimized collections +- private final Map, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); ++ private final Map, AttributeInstance> attributes = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(); // Leaf - Optimize AttributeMap + private final Set attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); + private final Set attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); + // Gale end - Lithium - replace AI attributes with optimized collections + private final AttributeSupplier supplier; +- private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations ++ //private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations // Leaf - Optimize AttributeMap + private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables + + public AttributeMap(AttributeSupplier supplier) { +@@ -31,7 +31,7 @@ public class AttributeMap { + this.entity = entity; + // Purpur end - Ridables + this.supplier = defaultAttributes; +- this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations ++ //this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations // Leaf - Optimize AttributeMap + } + + private void onAttributeModified(AttributeInstance instance) { +@@ -55,7 +55,17 @@ public class AttributeMap { + + @Nullable + public AttributeInstance getInstance(Holder attribute) { +- return this.attributes.computeIfAbsent(attribute, this.createInstance); // Gale - Airplane - reduce entity allocations - cache lambda, as for some reason java allocates it anyways ++ // Leaf start - Optimize AttributeMap ++ AttributeInstance v; ++ if ((v = this.attributes.get(attribute)) == null) { ++ AttributeInstance newValue; ++ if ((newValue = this.supplier.createInstance(this::onAttributeModified, attribute)) != null) { ++ this.attributes.put(attribute, newValue); ++ return newValue; ++ } ++ } ++ return v; ++ // Leaf end - Optimize AttributeMap + } + + public boolean hasAttribute(Holder attribute) { +diff --git a/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java b/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java +index 24710041ccbc70e5506d8d89ae34f0141977f209..09341ef6c651150aba223689badbead490162b2b 100644 +--- a/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java +@@ -11,7 +11,7 @@ public class AttributeSupplier { + private final Map, AttributeInstance> instances; + + AttributeSupplier(Map, AttributeInstance> instances) { +- this.instances = instances; ++ this.instances = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(instances); // Leaf - Optimize AttributeMap + } + + public AttributeInstance getAttributeInstance(Holder attribute) { diff --git a/leaf-server/minecraft-patches/features/0255-Optimize-getScaledTrackingDistance.patch b/leaf-server/minecraft-patches/features/0255-Optimize-getScaledTrackingDistance.patch new file mode 100644 index 00000000..c6fe72ac --- /dev/null +++ b/leaf-server/minecraft-patches/features/0255-Optimize-getScaledTrackingDistance.patch @@ -0,0 +1,25 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Sat, 17 May 2025 19:01:50 +0900 +Subject: [PATCH] Optimize getScaledTrackingDistance + + +diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java +index 747eb54f84650a9a507398e3d5352e001b042490..595284787053a5fb7385e8493953c73a19fe7aee 100644 +--- a/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -780,7 +780,13 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + + @Override + public int getScaledTrackingDistance(int trackingDistance) { +- return this.getProperties().entityBroadcastRangePercentage * trackingDistance / 100; ++ // Leaf start - Optimize getScaledTrackingDistance ++ int p = this.getProperties().entityBroadcastRangePercentage; ++ if (p == 100) { ++ return trackingDistance; ++ } ++ return p * trackingDistance / 100; ++ // Leaf end - Optimize getScaledTrackingDistance + } + + @Override diff --git a/leaf-server/minecraft-patches/features/0256-Optimize-SynchedEntityData-packDirty.patch b/leaf-server/minecraft-patches/features/0256-Optimize-SynchedEntityData-packDirty.patch new file mode 100644 index 00000000..38ad3bb4 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0256-Optimize-SynchedEntityData-packDirty.patch @@ -0,0 +1,27 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Sat, 17 May 2025 19:03:31 +0900 +Subject: [PATCH] Optimize SynchedEntityData#packDirty + + +diff --git a/net/minecraft/network/syncher/SynchedEntityData.java b/net/minecraft/network/syncher/SynchedEntityData.java +index f1ab0e66e7d464f7f31a7a360528ed97cdda0aa0..7ca540c70cd40815c0d4c18c2cae28a4cfd88e86 100644 +--- a/net/minecraft/network/syncher/SynchedEntityData.java ++++ b/net/minecraft/network/syncher/SynchedEntityData.java +@@ -84,7 +84,15 @@ public class SynchedEntityData { + return null; + } else { + this.isDirty = false; +- List> list = new ArrayList<>(); ++ // Leaf start - Optimize SynchedEntityData#packDirty ++ int cap = 0; ++ for (SynchedEntityData.DataItem dataItem : this.itemsById) { ++ if (dataItem.isDirty()) { ++ cap += 1; ++ } ++ } ++ ArrayList> list = new ArrayList<>(cap); ++ // Leaf end - Optimize SynchedEntityData#packDirty + + for (SynchedEntityData.DataItem dataItem : this.itemsById) { + if (dataItem.isDirty()) { diff --git a/leaf-server/minecraft-patches/features/0257-Optimize-isEyeInFluid.patch b/leaf-server/minecraft-patches/features/0257-Optimize-isEyeInFluid.patch new file mode 100644 index 00000000..929bedb9 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0257-Optimize-isEyeInFluid.patch @@ -0,0 +1,77 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: wling-art +Date: Sat, 17 May 2025 08:25:33 +0800 +Subject: [PATCH] Optimize isEyeInFluid + + +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index 6f3fd83c9f0bafe613c780ba56430084e91fdd2a..fbdc7e54df6cf28264b1dc0d395b9f91206cd50e 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -269,6 +269,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + protected Object2DoubleMap> fluidHeight = new Object2DoubleArrayMap<>(2); + protected boolean wasEyeInWater; + private final Set> fluidOnEyes = new HashSet<>(); ++ private static final int FLUID_WATER = 1; // Leaf - Optimize isEyeInFluid ++ private static final int FLUID_LAVA = 2; // Leaf - Optimize isEyeInFluid ++ private int fluidCache = 0; // Leaf - Optimize isEyeInFluid + public int invulnerableTime; + protected boolean firstTick = true; + protected final SynchedEntityData entityData; +@@ -1997,7 +2000,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + + private void updateFluidOnEyes() { + this.wasEyeInWater = this.isEyeInFluid(FluidTags.WATER); +- this.fluidOnEyes.clear(); ++ if (org.dreeam.leaf.config.modules.opt.EyeFluidCache.enabled) fluidCache = 0; else this.fluidOnEyes.clear(); // Leaf - Optimize isEyeInFluid ++ + double eyeY = this.getEyeY(); + if (!( + this.getVehicle() instanceof AbstractBoat abstractBoat +@@ -2009,7 +2013,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + FluidState fluidState = this.level().getFluidState(blockPos); + double d = blockPos.getY() + fluidState.getHeight(this.level(), blockPos); + if (d > eyeY) { +- this.fluidOnEyes.addAll(fluidState.getTagsAsSet()); // Leaf - Remove stream in updateFluidOnEyes ++ // Leaf start - Optimize isEyeInFluid ++ if (org.dreeam.leaf.config.modules.opt.EyeFluidCache.enabled) { ++ if (fluidState.is(FluidTags.WATER)) { ++ setFluidStatus(FluidTags.WATER, true); ++ } ++ if (fluidState.is(FluidTags.LAVA)) { ++ setFluidStatus(FluidTags.LAVA, true); ++ } ++ } else { ++ this.fluidOnEyes.addAll(fluidState.getTagsAsSet()); // Leaf - Remove stream in updateFluidOnEyes ++ } ++ // Leaf end - Optimize isEyeInFluid + } + } + } +@@ -2089,9 +2104,25 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + } + ++ // Leaf start - Optimize isEyeInFluid + public boolean isEyeInFluid(TagKey fluidTag) { +- return this.fluidOnEyes.contains(fluidTag); ++ if (!org.dreeam.leaf.config.modules.opt.EyeFluidCache.enabled) { ++ return this.fluidOnEyes.contains(fluidTag); ++ } ++ return fluidTag == FluidTags.WATER ? (fluidCache & FLUID_WATER) != 0 ++ : fluidTag == FluidTags.LAVA && (fluidCache & FLUID_LAVA) != 0; ++ } ++ ++ public void setFluidStatus(TagKey fluidTag, boolean isInFluid) { ++ int bit = fluidTag == FluidTags.WATER ? FLUID_WATER ++ : fluidTag == FluidTags.LAVA ? FLUID_LAVA ++ : 0; ++ ++ if (bit == 0) return; ++ ++ fluidCache = isInFluid ? (fluidCache | bit) : (fluidCache & ~bit); + } ++ // Leaf end - Optimize isEyeInFluid + + public boolean isInLava() { + return !this.firstTick && this.fluidHeight.getDouble(FluidTags.LAVA) > 0.0; diff --git a/leaf-server/minecraft-patches/features/0242-paw-optimization.patch b/leaf-server/minecraft-patches/features/0258-Paw-optimization.patch similarity index 84% rename from leaf-server/minecraft-patches/features/0242-paw-optimization.patch rename to leaf-server/minecraft-patches/features/0258-Paw-optimization.patch index c7898780..35c90d7d 100644 --- a/leaf-server/minecraft-patches/features/0242-paw-optimization.patch +++ b/leaf-server/minecraft-patches/features/0258-Paw-optimization.patch @@ -1,7 +1,7 @@ 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 +Subject: [PATCH] Paw optimization Some random optimizations @@ -10,10 +10,10 @@ Some random optimizations - Secret patches (WIP) diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java -index f3e9de8716f5e1a72ec465ee897c8f0413f7b1c3..f998cf8d70302a21289de4d84b46d322d0b8a8fe 100644 +index c83ee2137a57e62003b1d20c3ceea9f569350a53..de1f271d36c7daa10c398e146386b51e2622df9a 100644 --- a/net/minecraft/network/Connection.java +++ b/net/minecraft/network/Connection.java -@@ -617,13 +617,7 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -660,13 +660,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) { @@ -77,7 +77,7 @@ index 4535858701b2bb232b9d2feb2af6551526232ddc..e65c62dbe4c1560ae153e4c4344e9194 - // 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 +index 0f9d18dd29e210ad656da211a3cb1cb25cd4efb1..d1c36cd17c83e7e0167046093c4a2b8427c8bae0 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 @@ -94,7 +94,7 @@ index f89fb321e50338e7765476cb5d7bdf2f02a497b3..83fcef85ed2a5410275e4419e4356994 for (LevelChunk levelChunk : list) { diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 8ade900d016026cde482ccbca7a411993d9eadd9..414a70c352263a1cd2bfb938053990ea2be2b2c3 100644 +index 48e06770ee9789e0b64e7369e26f23eea72f691a..31599270e0827db49037d54b0bf5f7a78e3a5e5e 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 @@ -126,10 +126,10 @@ index 8ade900d016026cde482ccbca7a411993d9eadd9..414a70c352263a1cd2bfb938053990ea 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 +index fbdc7e54df6cf28264b1dc0d395b9f91206cd50e..fa315f46e9b917ef2329d7071c0790d7a4b8ed53 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 +@@ -1140,16 +1140,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess return this.onGround; } @@ -146,7 +146,7 @@ index 960ae487a5a88a5fe9899c16b9553cea0fbfba37..1ff8522b81eb4bc6297b44a1f2a48c80 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 +@@ -1157,16 +1147,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 @@ -163,7 +163,7 @@ index 960ae487a5a88a5fe9899c16b9553cea0fbfba37..1ff8522b81eb4bc6297b44a1f2a48c80 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 +@@ -1300,13 +1281,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess // Gale end - skip negligible planar movement multiplication } } @@ -177,7 +177,7 @@ index 960ae487a5a88a5fe9899c16b9553cea0fbfba37..1ff8522b81eb4bc6297b44a1f2a48c80 } private void applyMovementEmissionAndPlaySound(Entity.MovementEmission movementEmission, Vec3 movement, BlockPos pos, BlockState state) { -@@ -4771,9 +4745,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4801,9 +4775,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public void setDeltaMovement(Vec3 deltaMovement) { @@ -187,7 +187,7 @@ index 960ae487a5a88a5fe9899c16b9553cea0fbfba37..1ff8522b81eb4bc6297b44a1f2a48c80 } public void addDeltaMovement(Vec3 addend) { -@@ -4881,9 +4853,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4911,9 +4883,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) { @@ -197,3 +197,16 @@ index 960ae487a5a88a5fe9899c16b9553cea0fbfba37..1ff8522b81eb4bc6297b44a1f2a48c80 int floor = Mth.floor(x); int floor1 = Mth.floor(y); int floor2 = Mth.floor(z); +diff --git a/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java b/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java +index ef9c2c0a665a1acf490affd9cd4496ae9d677410..27e7c1bb585f30165bd501bb8f8aab0dd147ca5b 100644 +--- a/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java ++++ b/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java +@@ -48,7 +48,7 @@ public class DesertPyramidStructure extends SinglePieceStructure { + } + } + +- ObjectArrayList list = new ObjectArrayList<>(set.stream().toList()); ++ ObjectArrayList list = new ObjectArrayList<>(set); // Leaf - paw optimization - TODO: use array + RandomSource randomSource = RandomSource.create(level.getSeed()).forkPositional().at(pieces.calculateBoundingBox().getCenter()); + Util.shuffle(list, randomSource); + int min = Math.min(set.size(), randomSource.nextInt(5, 8)); diff --git a/leaf-server/minecraft-patches/features/0259-Cache-block-path-type.patch b/leaf-server/minecraft-patches/features/0259-Cache-block-path-type.patch new file mode 100644 index 00000000..8b50dc46 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0259-Cache-block-path-type.patch @@ -0,0 +1,122 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Fri, 23 May 2025 12:01:42 +0900 +Subject: [PATCH] Cache block path type + + +diff --git a/net/minecraft/server/Bootstrap.java b/net/minecraft/server/Bootstrap.java +index 83bc2f6b7774c8753a15dbeb00f0c9103713fd1b..20a720253092b4ec0b648edf06a048f92da201e0 100644 +--- a/net/minecraft/server/Bootstrap.java ++++ b/net/minecraft/server/Bootstrap.java +@@ -60,6 +60,7 @@ public class Bootstrap { + io.papermc.paper.world.worldgen.OptionallyFlatBedrockConditionSource.bootstrap(); // Paper - Flat bedrock generator settings + }); + // Paper end ++ net.minecraft.world.level.block.Blocks.initPathType(); // Leaf - Cache path type + CreativeModeTabs.validate(); + wrapStreams(); + bootstrapDuration.set(Duration.between(instant, Instant.now()).toMillis()); +diff --git a/net/minecraft/world/level/block/Blocks.java b/net/minecraft/world/level/block/Blocks.java +index 303bd27d44e4acfee49334235a6704724e3fd616..d001d0859c9508eb06f05010ab1cb8069f9b87cf 100644 +--- a/net/minecraft/world/level/block/Blocks.java ++++ b/net/minecraft/world/level/block/Blocks.java +@@ -7078,4 +7078,14 @@ public class Blocks { + } + } + } ++ ++ // Leaf start - Cache path type ++ public static void initPathType() { ++ for (Block block : BuiltInRegistries.BLOCK) { ++ for (BlockState blockState : block.getStateDefinition().getPossibleStates()) { ++ blockState.initPathType(); ++ } ++ } ++ } ++ // Leaf end - Cache path type + } +diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java +index 6d522e9485fadd6fc0f350cb30ba5224aa046d4f..611887f5f8f218f5ec1ad19580f3123a60b20d46 100644 +--- a/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -473,6 +473,7 @@ public abstract class BlockBehaviour implements FeatureElement { + private boolean emptyCollisionShape; + private boolean emptyConstantCollisionShape; + private VoxelShape constantCollisionShape; ++ public net.minecraft.world.level.pathfinder.PathType pathType; // Leaf - Cache path type + + private static void initCaches(final VoxelShape shape, final boolean neighbours) { + ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$isFullBlock(); +@@ -642,6 +643,12 @@ public abstract class BlockBehaviour implements FeatureElement { + // Paper end - optimise collisions + } + ++ // Leaf start - Cache path type ++ public void initPathType() { ++ pathType = net.minecraft.world.level.pathfinder.WalkNodeEvaluator.getPathTypeFromState(this.asState()); ++ } ++ // Leaf end - Cache path type ++ + public Block getBlock() { + return this.owner; + } +diff --git a/net/minecraft/world/level/pathfinder/PathTypeCache.java b/net/minecraft/world/level/pathfinder/PathTypeCache.java +index 3b190ba2a719cc45045d9be893884b50b9364b58..b44f6562a7f7651b2728ba49699031000b54b818 100644 +--- a/net/minecraft/world/level/pathfinder/PathTypeCache.java ++++ b/net/minecraft/world/level/pathfinder/PathTypeCache.java +@@ -24,7 +24,7 @@ public class PathTypeCache { + } + + private PathType compute(BlockGetter level, BlockPos pos, int index, long packedPos) { +- PathType pathTypeFromState = WalkNodeEvaluator.getPathTypeFromState(level, pos); ++ PathType pathTypeFromState = WalkNodeEvaluator.leafPathType(level, pos); // Leaf - Cache path type + this.positions[index] = packedPos; + this.pathTypes[index] = pathTypeFromState; + return pathTypeFromState; +diff --git a/net/minecraft/world/level/pathfinder/PathfindingContext.java b/net/minecraft/world/level/pathfinder/PathfindingContext.java +index 4eca1dd0819c7ee7a77e45fc5fa03f4ee5cdceaf..ff68b19b5fe4c80872d483363611b843146de989 100644 +--- a/net/minecraft/world/level/pathfinder/PathfindingContext.java ++++ b/net/minecraft/world/level/pathfinder/PathfindingContext.java +@@ -27,7 +27,7 @@ public class PathfindingContext { + + public PathType getPathTypeFromState(int x, int y, int z) { + BlockPos blockPos = this.mutablePos.set(x, y, z); +- return this.cache == null ? WalkNodeEvaluator.getPathTypeFromState(this.level, blockPos) : this.cache.getOrCompute(this.level, blockPos); ++ return this.cache == null ? WalkNodeEvaluator.leafPathType(this.level, blockPos) : this.cache.getOrCompute(this.level, blockPos); // Leaf - Cache path type + } + + public BlockState getBlockState(BlockPos pos) { +diff --git a/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java b/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java +index db6baaa698fe93aba3fbd595158b568badd6cb8a..5bd1300ef82a1d431c098d9cf75a7fa9d5a43502 100644 +--- a/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java ++++ b/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java +@@ -479,6 +479,16 @@ public class WalkNodeEvaluator extends NodeEvaluator { + return pathType; + } + ++ // Leaf start - Cache path type ++ public static PathType leafPathType(BlockGetter level, BlockPos blockPos) { ++ BlockState blockState = level.getBlockStateIfLoaded(blockPos); ++ if (blockState == null) { ++ return PathType.BLOCKED; ++ } ++ return blockState.pathType; ++ } ++ // Leaf end - Cache path type ++ + protected static PathType getPathTypeFromState(BlockGetter level, BlockPos pos) { + // Paper start - Do not load chunks during pathfinding + BlockState blockState = level.getBlockStateIfLoaded(pos); +@@ -486,6 +496,12 @@ public class WalkNodeEvaluator extends NodeEvaluator { + return PathType.BLOCKED; + } + // Paper end ++ // Leaf start - Cache path type ++ return getPathTypeFromState(blockState); ++ } ++ ++ public static PathType getPathTypeFromState(BlockState blockState) { ++ // Leaf end - Cache path type + Block block = blockState.getBlock(); + if (blockState.isAir()) { + return PathType.OPEN; diff --git a/leaf-server/minecraft-patches/features/0260-optimize-getEntityStatus.patch b/leaf-server/minecraft-patches/features/0260-optimize-getEntityStatus.patch new file mode 100644 index 00000000..ebe6b6cf --- /dev/null +++ b/leaf-server/minecraft-patches/features/0260-optimize-getEntityStatus.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Fri, 23 May 2025 15:57:42 +0900 +Subject: [PATCH] optimize getEntityStatus + + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java +index 7554c109c35397bc1a43dd80e87764fd78645bbf..151476fd036839a416c226599279d0d8bf79717b 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/EntityLookup.java +@@ -93,8 +93,14 @@ public abstract class EntityLookup implements LevelEntityGetter { + if (entity == null) { + return null; + } +- final Visibility visibility = EntityLookup.getEntityStatus(entity); +- return visibility.isAccessible() ? entity : null; ++ // Leaf start ++ final FullChunkStatus entityStatus = ((ChunkSystemEntity) entity).moonrise$getChunkStatus(); ++ return switch (entityStatus) { ++ case INACCESSIBLE -> null; ++ case FULL, BLOCK_TICKING, ENTITY_TICKING -> entity; ++ case null -> null; ++ }; ++ // Leaf end + } + + @Override +@@ -394,7 +400,14 @@ public abstract class EntityLookup implements LevelEntityGetter { + return Visibility.TICKING; + } + final FullChunkStatus entityStatus = ((ChunkSystemEntity)entity).moonrise$getChunkStatus(); +- return Visibility.fromFullChunkStatus(entityStatus == null ? FullChunkStatus.INACCESSIBLE : entityStatus); ++ // Leaf start ++ return switch (entityStatus) { ++ case INACCESSIBLE -> Visibility.HIDDEN; ++ case FULL, BLOCK_TICKING -> Visibility.TRACKED; ++ case ENTITY_TICKING -> Visibility.TICKING; ++ case null -> Visibility.HIDDEN; ++ }; ++ // Leaf end + } + + protected boolean addEntity(final Entity entity, final boolean fromDisk, final boolean event) { +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index fa315f46e9b917ef2329d7071c0790d7a4b8ed53..f130002b8bdfbf3ae9fa1a8166fd502924c5a42f 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -370,6 +370,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + // Paper end + // Paper start - rewrite chunk system + private final boolean isHardColliding = this.moonrise$isHardCollidingUncached(); ++ @org.jetbrains.annotations.Nullable // Leaf + private net.minecraft.server.level.FullChunkStatus chunkStatus; + private ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData chunkData; + private int sectionX = Integer.MIN_VALUE; +@@ -383,6 +384,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + @Override ++ @org.jetbrains.annotations.Nullable // Leaf + public final net.minecraft.server.level.FullChunkStatus moonrise$getChunkStatus() { + return this.chunkStatus; + } diff --git a/leaf-server/paper-patches/features/0042-Multithreaded-Tracker.patch b/leaf-server/paper-patches/features/0042-Multithreaded-Tracker.patch index 430b0f8f..4ee9acbc 100644 --- a/leaf-server/paper-patches/features/0042-Multithreaded-Tracker.patch +++ b/leaf-server/paper-patches/features/0042-Multithreaded-Tracker.patch @@ -40,6 +40,37 @@ index db031298c2090eb36032de4b52335c62186e4cfb..84905d7802f8a5c3f68e15f1b17ef082 throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index d891595b25cf04980cc24a6e6b9bd95553527100..216a273d65fc7eda8f2e229e4257b2708294849c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -738,7 +738,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + ChunkMap.TrackedEntity entityTracker = world.getChunkSource().chunkMap.entityMap.get(this.getEntityId()); + + if (entityTracker != null) { +- for (ServerPlayerConnection connection : entityTracker.seenBy) { ++ for (ServerPlayerConnection connection : entityTracker.seenBy()) { // Leaf - Multithreaded tracker + players.add(connection.getPlayer().getBukkitEntity()); + } + } +@@ -1054,7 +1054,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + return; + } + +- for (final ServerPlayerConnection connection : entityTracker.seenBy) { ++ for (final ServerPlayerConnection connection : entityTracker.seenBy()) { // Leaf - Multithreaded tracker + this.getHandle().resendPossiblyDesyncedEntityData(connection.getPlayer()); + } + } +@@ -1201,7 +1201,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + + Set set = new java.util.HashSet<>(tracker.seenBy.size()); +- for (net.minecraft.server.network.ServerPlayerConnection connection : tracker.seenBy) { ++ for (net.minecraft.server.network.ServerPlayerConnection connection : tracker.seenBy()) { // Leaf - Multithreaded tracker + set.add(connection.getPlayer().getBukkitEntity().getPlayer()); + } + return set; diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index f32316b0357f1cb0501a052361a0221f8e9d1438..ebb07b9b9f6bef9195978c8ecdd5f4ef3ee198bc 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java diff --git a/leaf-server/paper-patches/features/0051-Async-chunk-send.patch b/leaf-server/paper-patches/features/0051-Async-chunk-send.patch new file mode 100644 index 00000000..453ba318 --- /dev/null +++ b/leaf-server/paper-patches/features/0051-Async-chunk-send.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Fri, 2 May 2025 18:22:24 -0700 +Subject: [PATCH] Async chunk send + + +diff --git a/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java b/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java +index ca24f4cd7a50f0156d84e263c60f841cca95c669..7e15038e8fceab1e97c2245c2e9111deed6455fb 100644 +--- a/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java ++++ b/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java +@@ -185,7 +185,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo + return; + } + +- if (!Bukkit.isPrimaryThread()) { ++ if (!Bukkit.isPrimaryThread() && !(Thread.currentThread() instanceof org.dreeam.leaf.async.chunk.AsyncChunkSendThread)) { // Leaf - Async chunk send + // Plugins? + MinecraftServer.getServer().scheduleOnMain(() -> modifyBlocks(chunkPacket, chunkPacketInfo)); + return; diff --git a/leaf-server/paper-patches/features/0052-Optimise-player-movement-checks.patch b/leaf-server/paper-patches/features/0052-Optimise-player-movement-checks.patch new file mode 100644 index 00000000..1a0a67b7 --- /dev/null +++ b/leaf-server/paper-patches/features/0052-Optimise-player-movement-checks.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sun, 11 May 2025 00:37:44 +0200 +Subject: [PATCH] Optimise player movement checks + + +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/SingleUserAreaMap.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/SingleUserAreaMap.java +index 7e85bb58b8a5de17fdab9ad3c574f2f80952e1ca..8e424b48c06e90893f0f2fde7c35410bd8271469 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/misc/SingleUserAreaMap.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/SingleUserAreaMap.java +@@ -86,6 +86,11 @@ public abstract class SingleUserAreaMap { + if (fromX == NOT_SET) { + return false; + } ++ // Leaf start - Optimise player movement checks ++ if (org.dreeam.leaf.config.modules.opt.OptimizePlayerMovementProcessing.enabled && fromX == toX && fromZ == toZ && oldViewDistance == newViewDistance) { ++ return true; ++ } ++ // Leaf end - Optimise player movement checks + + this.lastChunkX = toX; + this.lastChunkZ = toZ; diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/AsyncChunkSending.java b/leaf-server/src/main/java/org/dreeam/leaf/async/AsyncChunkSending.java deleted file mode 100644 index a54cefb3..00000000 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/AsyncChunkSending.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.dreeam.leaf.async; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -public class AsyncChunkSending { - - public static final Logger LOGGER = LogManager.getLogger(AsyncChunkSending.class.getSimpleName()); -} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java b/leaf-server/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java index 40f552d1..3df32778 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java @@ -1,5 +1,6 @@ package org.dreeam.leaf.async; +import net.minecraft.Util; import org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave; import java.util.Optional; @@ -17,7 +18,7 @@ public class AsyncPlayerDataSaving { new com.google.common.util.concurrent.ThreadFactoryBuilder() .setPriority(Thread.NORM_PRIORITY - 2) .setNameFormat("Leaf IO Thread") - .setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)) + .setUncaughtExceptionHandler(Util::onThreadException) .build(), new ThreadPoolExecutor.DiscardPolicy() ); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java index b6a4c632..084ed5df 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java @@ -1,5 +1,6 @@ package org.dreeam.leaf.async.ai; +import it.unimi.dsi.fastutil.ints.IntArrayList; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Mob; @@ -8,80 +9,99 @@ import org.apache.logging.log4j.Logger; import org.dreeam.leaf.config.modules.async.AsyncTargetFinding; import org.dreeam.leaf.util.queue.SpscIntQueue; +import java.util.OptionalInt; import java.util.concurrent.locks.LockSupport; public class AsyncGoalExecutor { - public static final Logger LOGGER = LogManager.getLogger("Leaf Async Goal"); - + protected static final Logger LOGGER = LogManager.getLogger("Leaf Async Goal"); protected final SpscIntQueue queue; protected final SpscIntQueue wake; + protected final IntArrayList submit; private final AsyncGoalThread thread; - private final ServerLevel serverLevel; - private boolean dirty = false; - private long tickCount = 0L; - private static final int SPIN_LIMIT = 100; + private final ServerLevel world; + private long midTickCount = 0L; - public AsyncGoalExecutor(AsyncGoalThread thread, ServerLevel serverLevel) { - this.serverLevel = serverLevel; + public AsyncGoalExecutor(AsyncGoalThread thread, ServerLevel world) { + this.world = world; this.queue = new SpscIntQueue(AsyncTargetFinding.queueSize); this.wake = new SpscIntQueue(AsyncTargetFinding.queueSize); + this.submit = new IntArrayList(); this.thread = thread; } boolean wake(int id) { - Entity entity = this.serverLevel.getEntities().get(id); + Entity entity = this.world.getEntities().get(id); if (entity == null || entity.isRemoved() || !(entity instanceof Mob mob)) { return false; } - mob.goalSelector.wake(); - mob.targetSelector.wake(); + mob.goalSelector.ctx.wake(); + mob.targetSelector.ctx.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; + this.submit.add(entityId); } - public final void unpark() { - if (dirty) LockSupport.unpark(thread); - dirty = false; + public final void tick() { + batchSubmit(); + LockSupport.unpark(thread); + } + + private void batchSubmit() { + if (submit.isEmpty()) { + return; + } + int[] raw = submit.elements(); + int size = submit.size(); + for (int i = 0; i < size; i++) { + int id = raw[i]; + if (poll(id) && !this.queue.send(id)) { + do { + wake(id); + } while (poll(id)); + } + } + this.submit.clear(); } public final void midTick() { - boolean didWork = false; while (true) { - int id = this.wake.recv(); - if (id == Integer.MAX_VALUE) { + OptionalInt result = this.wake.recv(); + if (result.isEmpty()) { break; } - didWork = true; - Entity entity = this.serverLevel.getEntities().get(id); - if (entity == null || !entity.isAlive() || !(entity instanceof Mob mob)) { - continue; + int id = result.getAsInt(); + if (poll(id) && !this.queue.send(id)) { + do { + wake(id); + } while (poll(id)); } + } + if (AsyncTargetFinding.threshold <= 0L || (midTickCount % AsyncTargetFinding.threshold) == 0L) { + batchSubmit(); + } + midTickCount += 1; + } + + private boolean poll(int id) { + Entity entity = this.world.getEntities().get(id); + if (entity == null || entity.isRemoved() || !(entity instanceof Mob mob)) { + return false; + } + + try { mob.tickingTarget = true; boolean a = mob.targetSelector.poll(); mob.tickingTarget = false; boolean b = mob.goalSelector.poll(); - if (a || b) { - submit(id); - } + return a || b; + } catch (Exception e) { + LOGGER.error("Exception while polling", e); + // retry + return true; } - if (didWork || (tickCount & 15L) == 0L) unpark(); - tickCount += 1; } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java index d3d236c2..06bd5e48 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalThread.java @@ -4,12 +4,11 @@ import net.minecraft.Util; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; +import java.util.OptionalInt; 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); @@ -19,38 +18,29 @@ public class AsyncGoalThread extends Thread { } private static void run(MinecraftServer server) { - int emptySpins = 0; - while (server.isRunning()) { - boolean didWork = false; + boolean retry = 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) { + OptionalInt result = exec.queue.recv(); + if (result.isEmpty()) { break; } - levelWork = true; + int id = result.getAsInt(); + retry = true; if (exec.wake(id)) { while (!exec.wake.send(id)) { Thread.onSpinWait(); } } } - didWork |= levelWork; + + Thread.yield(); } - // 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 - } + + if (!retry) { + LockSupport.parkNanos(10_000L); } } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/VWaker.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/VWaker.java new file mode 100644 index 00000000..f664cfbf --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/VWaker.java @@ -0,0 +1,8 @@ +package org.dreeam.leaf.async.ai; + +import org.jetbrains.annotations.Nullable; + +@FunctionalInterface +public interface VWaker { + @Nullable Object wake(); +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java index 0f064e5e..37e557ef 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java @@ -5,14 +5,26 @@ import org.jetbrains.annotations.Nullable; public class Waker { @Nullable - public volatile Runnable wake = null; + public volatile VWaker wake = null; @Nullable public volatile Object result = null; - public volatile boolean state = true; + public boolean state = true; public final @Nullable Object result() { Object result = this.result; this.result = null; return result; } + + final void wake() { + final var wake = this.wake; + if (wake != null) { + try { + this.result = wake.wake(); + } catch (Exception e) { + AsyncGoalExecutor.LOGGER.error("Exception while wake", e); + } + this.wake = null; + } + } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/chunk/AsyncChunkSend.java b/leaf-server/src/main/java/org/dreeam/leaf/async/chunk/AsyncChunkSend.java new file mode 100644 index 00000000..d5fcecaf --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/chunk/AsyncChunkSend.java @@ -0,0 +1,26 @@ +package org.dreeam.leaf.async.chunk; + +import net.minecraft.Util; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class AsyncChunkSend { + + public static final ExecutorService POOL = new ThreadPoolExecutor( + 1, 1, 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(), + new com.google.common.util.concurrent.ThreadFactoryBuilder() + .setPriority(Thread.NORM_PRIORITY - 2) + .setNameFormat("Leaf Async Chunk Send Thread") + .setUncaughtExceptionHandler(Util::onThreadException) + .setThreadFactory(AsyncChunkSendThread::new) + .build(), + new ThreadPoolExecutor.DiscardPolicy() + ); + public static final Logger LOGGER = LogManager.getLogger("Leaf Async Chunk Send"); +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/chunk/AsyncChunkSendThread.java b/leaf-server/src/main/java/org/dreeam/leaf/async/chunk/AsyncChunkSendThread.java new file mode 100644 index 00000000..4dae48a5 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/chunk/AsyncChunkSendThread.java @@ -0,0 +1,8 @@ +package org.dreeam.leaf.async.chunk; + +public class AsyncChunkSendThread extends Thread { + + protected AsyncChunkSendThread(Runnable task) { + super(task); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/locate/AsyncLocator.java b/leaf-server/src/main/java/org/dreeam/leaf/async/locate/AsyncLocator.java index e2d70fe1..c5541c9b 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/locate/AsyncLocator.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/locate/AsyncLocator.java @@ -12,7 +12,12 @@ import net.minecraft.tags.TagKey; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.levelgen.structure.Structure; -import java.util.concurrent.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java index af0f16a8..12ae72fe 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java @@ -1,10 +1,8 @@ package org.dreeam.leaf.async.path; import com.google.common.util.concurrent.ThreadFactoryBuilder; - import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.pathfinder.Path; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; @@ -12,7 +10,13 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Consumer; /** @@ -97,4 +101,5 @@ public class AsyncPathProcessor { final int queueCapacity = org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingQueueSize; return new LinkedBlockingQueue<>(queueCapacity); - }} + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorCache.java b/leaf-server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorCache.java index b95c8fc5..c98a0e2b 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorCache.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorCache.java @@ -2,7 +2,6 @@ package org.dreeam.leaf.async.path; import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; import net.minecraft.world.level.pathfinder.NodeEvaluator; - import org.apache.commons.lang3.Validate; import org.jetbrains.annotations.NotNull; diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorType.java b/leaf-server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorType.java index c0527323..c94f4c8b 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorType.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/path/NodeEvaluatorType.java @@ -1,6 +1,9 @@ package org.dreeam.leaf.async.path; -import net.minecraft.world.level.pathfinder.*; +import net.minecraft.world.level.pathfinder.AmphibiousNodeEvaluator; +import net.minecraft.world.level.pathfinder.FlyNodeEvaluator; +import net.minecraft.world.level.pathfinder.NodeEvaluator; +import net.minecraft.world.level.pathfinder.SwimNodeEvaluator; public enum NodeEvaluatorType { WALK, diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java b/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java index 91b48ee0..d2025803 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java @@ -2,12 +2,14 @@ package org.dreeam.leaf.async.tracker; import ca.spottedleaf.moonrise.common.list.ReferenceList; import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; -import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel; import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup; import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +import net.minecraft.Util; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerEntity; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import org.apache.logging.log4j.LogManager; @@ -16,30 +18,48 @@ import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; public class MultithreadedTracker { private static final String THREAD_PREFIX = "Leaf Async Tracker"; private static final Logger LOGGER = LogManager.getLogger(THREAD_PREFIX); private static long lastWarnMillis = System.currentTimeMillis(); - private static final ThreadPoolExecutor trackerExecutor = new ThreadPoolExecutor( - getCorePoolSize(), - getMaxPoolSize(), - getKeepAliveTime(), TimeUnit.SECONDS, - getQueueImpl(), - getThreadFactory(), - getRejectedPolicy() - ); + private static ThreadPoolExecutor TRACKER_EXECUTOR = null; + + private record SendChanges(Object[] entities, int size) implements Runnable { + @Override + public void run() { + for (int i = 0; i < size; i++) { + ((ServerEntity) entities[i]).sendDirtyEntityData(); + } + } + } private MultithreadedTracker() { } - public static Executor getTrackerExecutor() { - return trackerExecutor; + public static void init() { + if (TRACKER_EXECUTOR == null) { + TRACKER_EXECUTOR = new ThreadPoolExecutor( + getCorePoolSize(), + getMaxPoolSize(), + getKeepAliveTime(), TimeUnit.SECONDS, + getQueueImpl(), + getThreadFactory(), + getRejectedPolicy() + ); + } else { + throw new IllegalStateException(); + } } - public static void tick(ChunkSystemServerLevel level) { + public static void tick(ServerLevel level) { try { if (!org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) { tickAsync(level); @@ -51,7 +71,7 @@ public class MultithreadedTracker { } } - private static void tickAsync(ChunkSystemServerLevel level) { + private static void tickAsync(ServerLevel level) { final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers(); final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup(); @@ -59,7 +79,8 @@ public class MultithreadedTracker { final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); // Move tracking to off-main - trackerExecutor.execute(() -> { + TRACKER_EXECUTOR.execute(() -> { + ReferenceArrayList sendDirty = new ReferenceArrayList<>(); for (final Entity entity : trackerEntitiesRaw) { if (entity == null) continue; @@ -67,19 +88,30 @@ public class MultithreadedTracker { if (tracker == null) continue; - tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); - tracker.serverEntity.sendChanges(); + // Don't Parallel Tick Tracker of Entity + synchronized (tracker.sync) { + tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); + tracker.serverEntity.sendChanges(); + if (tracker.serverEntity.wantSendDirtyEntityData) { + tracker.serverEntity.wantSendDirtyEntityData = false; + sendDirty.add(tracker.serverEntity); + } + } + } + if (!sendDirty.isEmpty()) { + level.getServer().execute(new SendChanges(sendDirty.elements(), sendDirty.size())); } }); } - private static void tickAsyncWithCompatMode(ChunkSystemServerLevel level) { + private static void tickAsyncWithCompatMode(ServerLevel level) { final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers(); final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup(); final ReferenceList trackerEntities = entityLookup.trackerEntities; final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); final Runnable[] sendChangesTasks = new Runnable[trackerEntitiesRaw.length]; + final Runnable[] tickTask = new Runnable[trackerEntitiesRaw.length]; int index = 0; for (final Entity entity : trackerEntitiesRaw) { @@ -89,30 +121,54 @@ public class MultithreadedTracker { if (tracker == null) continue; - tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); - sendChangesTasks[index++] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array + synchronized (tracker.sync) { + tickTask[index] = tracker.leafTickCompact(nearbyPlayers.getChunk(entity.chunkPosition())); + sendChangesTasks[index] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array + } + index++; } // batch submit tasks - trackerExecutor.execute(() -> { + TRACKER_EXECUTOR.execute(() -> { + for (final Runnable tick : tickTask) { + if (tick == null) continue; + + tick.run(); + } for (final Runnable sendChanges : sendChangesTasks) { if (sendChanges == null) continue; sendChanges.run(); } + + ReferenceArrayList sendDirty = new ReferenceArrayList<>(); + for (final Entity entity : trackerEntitiesRaw) { + if (entity == null) continue; + + final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity(); + + if (tracker == null) continue; + if (tracker.serverEntity.wantSendDirtyEntityData) { + tracker.serverEntity.wantSendDirtyEntityData = false; + sendDirty.add(tracker.serverEntity); + } + } + if (!sendDirty.isEmpty()) { + level.getServer().execute(new SendChanges(sendDirty.elements(), sendDirty.size())); + } }); } // Original ChunkMap#newTrackerTick of Paper // Just for diff usage for future update private static void tickOriginal(ServerLevel level) { - final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup) ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) level).moonrise$getEntityLookup(); + final ServerEntityLookup entityLookup = (ServerEntityLookup) ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) level).moonrise$getEntityLookup(); - final ca.spottedleaf.moonrise.common.list.ReferenceList trackerEntities = entityLookup.trackerEntities; + final ReferenceList trackerEntities = entityLookup.trackerEntities; final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); for (int i = 0, len = trackerEntities.size(); i < len; ++i) { final Entity entity = trackerEntitiesRaw[i]; - final ChunkMap.TrackedEntity tracker = ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity) entity).moonrise$getTrackedEntity(); + final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity(); if (tracker == null) { continue; } @@ -147,6 +203,7 @@ public class MultithreadedTracker { .setThreadFactory(MultithreadedTrackerThread::new) .setNameFormat(THREAD_PREFIX + " Thread - %d") .setPriority(Thread.NORM_PRIORITY - 2) + .setUncaughtExceptionHandler(Util::onThreadException) .build(); } 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 230e6b77..e40a1c50 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 @@ -4,9 +4,15 @@ import io.papermc.paper.configuration.GlobalConfiguration; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.minecraft.Util; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.dreeam.leaf.config.modules.misc.SentryDSN; -import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; import java.io.File; import java.io.IOException; @@ -30,13 +36,6 @@ import java.util.concurrent.CompletableFuture; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; - /* * Yoinked from: https://github.com/xGinko/AnarchyExploitFixes/ & https://github.com/LuminolMC/Luminol * @author: @xGinko & @MrHua269 @@ -223,6 +222,11 @@ public class LeafConfig { "config/gale-world-defaults.yml" )); + @Nullable String existing = System.getProperty("spark.serverconfigs.extra"); + if (existing != null) { + extraConfigs.addAll(Arrays.asList(existing.split(","))); + } + for (World world : Bukkit.getWorlds()) { extraConfigs.add(world.getWorldFolder().getName() + "/gale-world.yml"); // Gale world config } @@ -230,10 +234,13 @@ public class LeafConfig { return extraConfigs; } - private static String[] buildSparkHiddenPaths() { - return new String[]{ - SentryDSN.sentryDsnConfigPath // Hide Sentry DSN key - }; + private static List buildSparkHiddenPaths() { + @Nullable String existing = System.getProperty("spark.serverconfigs.hiddenpaths"); + + List extraHidden = existing != null ? new ArrayList<>(Arrays.asList(existing.split(","))) : new ArrayList<>(); + extraHidden.add(SentryDSN.sentryDsnConfigPath); // Hide Sentry DSN key + + return extraHidden; } public static void regSparkExtraConfig() { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/Experimental.java b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/Experimental.java index 26a6967c..acc8aa7f 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/Experimental.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/annotations/Experimental.java @@ -1,6 +1,10 @@ package org.dreeam.leaf.config.annotations; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** * Indicates that a feature is experimental and may be removed or changed in the future. 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 89ef74ed..5313cb62 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 @@ -10,6 +10,7 @@ public class AsyncChunkSend extends ConfigModules { } public static boolean enabled = false; + private static boolean asyncChunkSendInitialized; @Override public void onLoaded() { @@ -20,6 +21,12 @@ public class AsyncChunkSend extends ConfigModules { 使区块数据包准备和发送异步化以提高服务器性能. 当许多玩家同时加载区块时, 这可以显著减少主线程负载."""); + if (asyncChunkSendInitialized) { + config.getConfigSection(getBasePath()); + return; + } + asyncChunkSendInitialized = 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 f24fc20f..003458e5 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 @@ -21,15 +21,15 @@ public class AsyncPathfinding extends ConfigModules { @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.""", + 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: 新提交的任务会被直接丢弃.""" + 当队列满时, 新提交的任务将使用以下策略处理. + FLUSH_ALL: 所有等待中的任务都将在主线程上运行. + CALLER_RUNS: 新提交的任务将在主线程上运行. + DISCARD: 新提交的任务会被直接丢弃.""" ); if (asyncPathfindingInitialized) { config.getConfigSection(getBasePath()); 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 7b8fecfe..6d555ce0 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 @@ -2,7 +2,6 @@ package org.dreeam.leaf.config.modules.async; import org.dreeam.leaf.config.ConfigModules; import org.dreeam.leaf.config.EnumConfigCategory; -import org.dreeam.leaf.config.annotations.Experimental; public class AsyncPlayerDataSave extends ConfigModules { 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 478f132c..c2ebeb42 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 @@ -3,7 +3,6 @@ package org.dreeam.leaf.config.modules.async; import org.dreeam.leaf.config.ConfigModules; import org.dreeam.leaf.config.EnumConfigCategory; -import org.dreeam.leaf.config.annotations.Experimental; public class AsyncTargetFinding extends ConfigModules { @@ -16,6 +15,7 @@ public class AsyncTargetFinding extends ConfigModules { public static boolean searchBlock = true; public static boolean searchEntity = true; public static int queueSize = 4096; + public static long threshold = 10L; private static boolean asyncTargetFindingInitialized; @Override @@ -36,11 +36,15 @@ public class AsyncTargetFinding extends ConfigModules { alertOther = config.getBoolean(getBasePath() + ".async-alert-other", true); searchBlock = config.getBoolean(getBasePath() + ".async-search-block", true); searchEntity = config.getBoolean(getBasePath() + ".async-search-entity", true); - queueSize = config.getInt(getBasePath() + ".queue-size", 4096); + queueSize = config.getInt(getBasePath() + ".queue-size", 0); + threshold = config.getLong(getBasePath() + ".threshold", 0); if (queueSize <= 0) { queueSize = 4096; } + if (threshold == 0L) { + threshold = 10L; + } if (!enabled) { alertOther = false; searchEntity = 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 a6b42058..7a01b4b1 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 @@ -57,5 +57,8 @@ public class MultithreadedTracker extends ConfigModules { if (asyncEntityTrackerQueueSize <= 0) asyncEntityTrackerQueueSize = asyncEntityTrackerMaxThreads * 384; + if (enabled) { + org.dreeam.leaf.async.tracker.MultithreadedTracker.init(); + } } } 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 index 3338c643..ce78ab95 100644 --- 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 @@ -10,7 +10,7 @@ public class ConfigurableInventoryOverflowEvent extends ConfigModules { } public static boolean enabled = false; - public static String listenerClass = "com.example.package.PlayerInventoryOverflowEvent" ; + public static String listenerClass = "com.example.package.PlayerInventoryOverflowEvent"; @Override public void onLoaded() { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/DeathItemDropKnockback.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/DeathItemDropKnockback.java new file mode 100644 index 00000000..7807c456 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/DeathItemDropKnockback.java @@ -0,0 +1,36 @@ +package org.dreeam.leaf.config.modules.gameplay; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class DeathItemDropKnockback extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".death-item-drop-knockback"; + } + + public static boolean dropAround = true; + public static double horizontalForce = 0.5; + public static double verticalForce = 0.2; + + @Override + public void onLoaded() { + dropAround = config.getBoolean(getBasePath() + ".drop-around", dropAround, + config.pickStringRegionBased( + "If true, items will drop randomly around the player on death.", + "如果为 “true”,物品会在玩家死亡时随机掉落在其周围." + )); + + horizontalForce = config.getDouble(getBasePath() + ".horizontal-force", horizontalForce, + config.pickStringRegionBased( + "Base speed for horizontal velocity when randomly dropping items.", + "随机掉落物品时水平速度的基本速度." + )); + + verticalForce = config.getDouble(getBasePath() + ".vertical-force", verticalForce, + config.pickStringRegionBased( + "Upward motion for randomly dropped items.", + "随机掉落物品的向上运动." + )); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/Knockback.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/Knockback.java index 916500e7..b0610c61 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/Knockback.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/Knockback.java @@ -2,6 +2,7 @@ package org.dreeam.leaf.config.modules.gameplay; import org.dreeam.leaf.config.ConfigModules; import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.config.annotations.Experimental; public class Knockback extends ConfigModules { @@ -12,6 +13,8 @@ public class Knockback extends ConfigModules { public static boolean snowballCanKnockback = false; public static boolean eggCanKnockback = false; public static boolean canPlayerKnockbackZombie = true; + @Experimental + public static boolean flushKnockback = false; @Override public void onLoaded() { @@ -30,5 +33,6 @@ public class Knockback extends ConfigModules { "Make players can knockback zombie.", "使玩家可以击退僵尸." )); + flushKnockback = config.getBoolean(getBasePath() + ".flush-location-while-knockback-player", flushKnockback); } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/SpawnerSettings.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/SpawnerSettings.java index 31e81d18..ae5e01f1 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/SpawnerSettings.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/SpawnerSettings.java @@ -18,6 +18,7 @@ public class SpawnerSettings extends ConfigModules { public static boolean checkForNearbyPlayers = true; public static boolean spawnerBlockChecks = false; public static boolean waterPreventSpawnCheck = false; + public static boolean ignoreSpawnRules = false; public static int minSpawnDelay = 200; public static int maxSpawnDelay = 800; @@ -60,8 +61,8 @@ public class SpawnerSettings extends ConfigModules { spawnerBlockChecks = config.getBoolean(getBasePath() + ".checks.spawner-block-checks", spawnerBlockChecks, config.pickStringRegionBased( - "Check if there are blocks blocking the spawner to spawn the mob", - "检查是否有方块阻挡刷怪笼生成怪物" + "Check if there are physical blocks obstructing the spawn location, or if custom spawn rules (isValidPosition) fail due to block conditions.", + "检查是否有物理方块阻挡生成位置, 或自定义生成规则(isValidPosition)因方块条件失败." )); waterPreventSpawnCheck = config.getBoolean(getBasePath() + ".checks.water-prevent-spawn-check", waterPreventSpawnCheck, @@ -69,6 +70,11 @@ public class SpawnerSettings extends ConfigModules { "Checks if there is water around that prevents spawning", "检查周围是否有水阻止生成" )); + ignoreSpawnRules = config.getBoolean(getBasePath() + ".checks.ignore-spawn-rules", ignoreSpawnRules, + config.pickStringRegionBased( + "Ignore mob-specific spawn rules, like animals needing grass or specific biomes/blocks (does not affect light level or physical obstruction checks).", + "忽略特定于生物的生成规则, 例如动物需要草方块或特定的生物群系/方块 (不影响光照等级或物理障碍物检查)." + )); // Delay settings diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/Cache.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/Cache.java index 132f820c..d7d6e7fc 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/Cache.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/Cache.java @@ -9,7 +9,7 @@ public class Cache extends ConfigModules { return EnumConfigCategory.MISC.getBaseKeyName() + ".cache"; } - public static boolean cachePlayerProfileResult = true; + public static boolean cachePlayerProfileResult = false; public static int cachePlayerProfileResultTimeout = 1440; @Override diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/UnknownCommandMessage.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/UnknownCommandMessage.java index 8c20b8df..d659fbb0 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/UnknownCommandMessage.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/misc/UnknownCommandMessage.java @@ -9,15 +9,19 @@ public class UnknownCommandMessage extends ConfigModules { return EnumConfigCategory.MISC.getBaseKeyName() + ".message"; } - public static String unknownCommandMessage = ""; + public static String unknownCommandMessage = "default"; @Override public void onLoaded() { unknownCommandMessage = config.getString(getBasePath() + ".unknown-command", unknownCommandMessage, config.pickStringRegionBased(""" Unknown command message, using MiniMessage format, set to "default" to use vanilla message, - placeholder: , shows detail of the unknown command information.""", + placeholder: + , show message of the command exception. + , shows detail of the command exception.""", """ 发送未知命令时的消息, 使用 MiniMessage 格式, 设置为 "default" 使用原版消息. - 变量: , 显示未知命令详细信息.""")); + 变量: + , 显示命令错误所附提示消息. + , 显示命令错误详细信息.""")); } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/AlternativeJoin.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/AlternativeJoin.java new file mode 100644 index 00000000..8176ce30 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/network/AlternativeJoin.java @@ -0,0 +1,20 @@ +package org.dreeam.leaf.config.modules.network; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class AlternativeJoin extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.NETWORK.getBaseKeyName(); + } + + public static boolean enabled = false; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".async-switch-state", enabled, config.pickStringRegionBased( + "Async switch connection state.", + "异步切换连接状态.")); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/CheckSurvivalBeforeGrowth.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/CheckSurvivalBeforeGrowth.java new file mode 100644 index 00000000..0058b1b8 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/CheckSurvivalBeforeGrowth.java @@ -0,0 +1,22 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class CheckSurvivalBeforeGrowth extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName() + ".check-survival-before-growth"; + } + + public static boolean cactusCheckSurvivalBeforeGrowth = false; + + @Override + public void onLoaded() { + cactusCheckSurvivalBeforeGrowth = config.getBoolean(getBasePath() + ".cactus-check-survival", cactusCheckSurvivalBeforeGrowth, + config.pickStringRegionBased(""" + Check if a cactus can survive before growing.""", + """ + 在仙人掌生长前检查其是否能够存活。""")); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/EyeFluidCache.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/EyeFluidCache.java new file mode 100644 index 00000000..36f3fdb9 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/EyeFluidCache.java @@ -0,0 +1,21 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class EyeFluidCache extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName(); + } + + public static boolean enabled = false; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".cache-eye-fluid-status", enabled, + config.pickStringRegionBased( + "Whether to cache the isEyeInFluid method to improve performance and reduce memory usage.", + "是否为 isEyeInFluid 方法启用缓存,以优化性能并减少内存使用.")); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimiseBlockEntities.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimiseBlockEntities.java new file mode 100644 index 00000000..e1ddf5eb --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimiseBlockEntities.java @@ -0,0 +1,18 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class OptimiseBlockEntities extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName(); + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".optimise-block-entities", enabled); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizeItemTicking.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizeItemTicking.java new file mode 100644 index 00000000..ea153f1f --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizeItemTicking.java @@ -0,0 +1,21 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class OptimizeItemTicking extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName(); + } + + public static boolean onlyTickItemsInHand = false; + + @Override + public void onLoaded() { + onlyTickItemsInHand = config.getBoolean(getBasePath() + ".only-tick-items-in-hand", onlyTickItemsInHand, config.pickStringRegionBased(""" + Whether to only tick / update items in main hand and offhand instead of the entire inventory.""", + """ + 是否只对主手和副手中的物品进行 tick / 更新,而不是整个物品栏中的所有物品。""")); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizePlayerMovementProcessing.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizePlayerMovementProcessing.java new file mode 100644 index 00000000..3f407850 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizePlayerMovementProcessing.java @@ -0,0 +1,22 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class OptimizePlayerMovementProcessing extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName(); + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".optimize-player-movement", enabled, config.pickStringRegionBased(""" + Whether to optimize player movement processing by skipping unnecessary edge checks and avoiding redundant view distance updates.""", + """ + 是否优化玩家移动处理,跳过不必要的边缘检查并避免冗余的视距更新。""")); + + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/ReduceChunkSourceUpdates.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/ReduceChunkSourceUpdates.java new file mode 100644 index 00000000..e89a0ad2 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/ReduceChunkSourceUpdates.java @@ -0,0 +1,23 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class ReduceChunkSourceUpdates extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName() + ".reduce-chunk-source-updates"; + } + + public static boolean enabled = false; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".enabled", enabled, + config.pickStringRegionBased( + "Reduces chunk source updates on inter-chunk player moves. (Recommended to enable)", + "减少玩家跨区块移动时的区块源更新。" + ) + ); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/protocol/DoABarrelRollPackets.java b/leaf-server/src/main/java/org/dreeam/leaf/protocol/DoABarrelRollPackets.java new file mode 100644 index 00000000..7aebf5b6 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/protocol/DoABarrelRollPackets.java @@ -0,0 +1,149 @@ +package org.dreeam.leaf.protocol; + +import io.netty.buffer.ByteBuf; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; + +public class DoABarrelRollPackets { + + private static LeafCustomPayload.@NotNull Type createType(String path) { + return new LeafCustomPayload.Type<>(ResourceLocation.fromNamespaceAndPath(DoABarrelRollProtocol.NAMESPACE, path)); + } + + public record ConfigResponseC2SPacket(int protocolVersion, boolean success) implements LeafCustomPayload { + public static final Type TYPE = createType("config_response"); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.INT, ConfigResponseC2SPacket::protocolVersion, + ByteBufCodecs.BOOL, ConfigResponseC2SPacket::success, + ConfigResponseC2SPacket::new + ); + + @Override + public @NotNull Type type() { + return TYPE; + } + } + + public record ConfigSyncS2CPacket(int protocolVersion, + LimitedModConfigServer applicableConfig, + boolean isLimited, + ModConfigServer fullConfig + ) implements LeafCustomPayload { + public static final Type TYPE = createType("config_sync"); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.INT, ConfigSyncS2CPacket::protocolVersion, + LimitedModConfigServer.getCodec(), ConfigSyncS2CPacket::applicableConfig, + ByteBufCodecs.BOOL, ConfigSyncS2CPacket::isLimited, + ModConfigServer.PACKET_CODEC, ConfigSyncS2CPacket::fullConfig, + ConfigSyncS2CPacket::new + ); + + @Override + public @NotNull Type type() { + return TYPE; + } + } + + public record ConfigUpdateAckS2CPacket(int protocolVersion, boolean success) implements LeafCustomPayload { + public static final Type TYPE = createType("config_update_ack"); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.INT, ConfigUpdateAckS2CPacket::protocolVersion, + ByteBufCodecs.BOOL, ConfigUpdateAckS2CPacket::success, + ConfigUpdateAckS2CPacket::new + ); + + @Override + public @NotNull Type type() { + return TYPE; + } + } + + public record ConfigUpdateC2SPacket(int protocolVersion, ModConfigServer config) implements LeafCustomPayload { + public static final Type TYPE = createType("config_update"); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.INT, ConfigUpdateC2SPacket::protocolVersion, + ModConfigServer.PACKET_CODEC, ConfigUpdateC2SPacket::config, + ConfigUpdateC2SPacket::new + ); + + @Override + public @NotNull Type type() { + return TYPE; + } + } + + public record RollSyncC2SPacket(boolean rolling, float roll) implements LeafCustomPayload { + public static final Type TYPE = createType("roll_sync"); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.BOOL, RollSyncC2SPacket::rolling, + ByteBufCodecs.FLOAT, RollSyncC2SPacket::roll, + RollSyncC2SPacket::new + ); + + @Override + public @NotNull Type type() { + return TYPE; + } + } + + public record RollSyncS2CPacket(int entityId, boolean rolling, float roll) implements LeafCustomPayload { + public static final Type TYPE = createType("roll_sync"); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.INT, RollSyncS2CPacket::entityId, + ByteBufCodecs.BOOL, RollSyncS2CPacket::rolling, + ByteBufCodecs.FLOAT, RollSyncS2CPacket::roll, + RollSyncS2CPacket::new + ); + + @Override + public @NotNull Type type() { + return TYPE; + } + } + + public interface LimitedModConfigServer { + boolean allowThrusting(); + + boolean forceEnabled(); + + static StreamCodec getCodec() { + return StreamCodec.composite( + ByteBufCodecs.BOOL, LimitedModConfigServer::allowThrusting, + ByteBufCodecs.BOOL, LimitedModConfigServer::forceEnabled, + Impl::new + ); + } + + record Impl(boolean allowThrusting, boolean forceEnabled) implements LimitedModConfigServer { + } + } + + public record ModConfigServer(boolean allowThrusting, + boolean forceEnabled, + boolean forceInstalled, + int installedTimeout, + KineticDamage kineticDamage + ) implements LimitedModConfigServer { + public static final StreamCodec PACKET_CODEC = StreamCodec.composite( + ByteBufCodecs.BOOL, ModConfigServer::allowThrusting, + ByteBufCodecs.BOOL, ModConfigServer::forceEnabled, + ByteBufCodecs.BOOL, ModConfigServer::forceInstalled, + ByteBufCodecs.INT, ModConfigServer::installedTimeout, + KineticDamage.CODEC, ModConfigServer::kineticDamage, + ModConfigServer::new + ); + } + + public enum KineticDamage { + VANILLA, + HIGH_SPEED, + NONE, + INSTANT_KILL; + + public static final StreamCodec CODEC = + ByteBufCodecs.STRING_UTF8.map(KineticDamage::valueOf, KineticDamage::name); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/protocol/DoABarrelRollProtocol.java b/leaf-server/src/main/java/org/dreeam/leaf/protocol/DoABarrelRollProtocol.java new file mode 100644 index 00000000..af88f866 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/protocol/DoABarrelRollProtocol.java @@ -0,0 +1,313 @@ +package org.dreeam.leaf.protocol; + +import com.google.common.collect.ImmutableList; +import it.unimi.dsi.fastutil.objects.Reference2BooleanMap; +import it.unimi.dsi.fastutil.objects.Reference2BooleanMaps; +import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2FloatMap; +import it.unimi.dsi.fastutil.objects.Reference2FloatMaps; +import it.unimi.dsi.fastutil.objects.Reference2FloatOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.server.network.ServerPlayerConnection; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.dreeam.leaf.protocol.DoABarrelRollPackets.ConfigResponseC2SPacket; +import org.dreeam.leaf.protocol.DoABarrelRollPackets.ConfigSyncS2CPacket; +import org.dreeam.leaf.protocol.DoABarrelRollPackets.ConfigUpdateAckS2CPacket; +import org.dreeam.leaf.protocol.DoABarrelRollPackets.ConfigUpdateC2SPacket; +import org.dreeam.leaf.protocol.DoABarrelRollPackets.KineticDamage; +import org.dreeam.leaf.protocol.DoABarrelRollPackets.ModConfigServer; +import org.dreeam.leaf.protocol.DoABarrelRollPackets.RollSyncC2SPacket; +import org.dreeam.leaf.protocol.DoABarrelRollPackets.RollSyncS2CPacket; +import org.jetbrains.annotations.NotNull; +import org.bukkit.event.player.PlayerKickEvent; + +import java.util.List; +import java.util.OptionalInt; + +public class DoABarrelRollProtocol implements Protocol { + + protected static final String NAMESPACE = "do_a_barrel_roll"; + private static final Logger LOGGER = LogManager.getLogger(NAMESPACE); + private static final int PROTOCOL_VERSION = 4; + private static final ModConfigServer DEFAULT = new ModConfigServer(false, false, false, 40, KineticDamage.VANILLA); + private static final Component SYNC_TIMEOUT_MESSAGE = Component.literal("Please install Do a Barrel Roll 2.4.0 or later to play on this server."); + private static DoABarrelRollProtocol INSTANCE = null; + + private final List> c2s = ImmutableList.of( + new Protocols.TypeAndCodec<>(ConfigUpdateC2SPacket.TYPE, ConfigUpdateC2SPacket.STREAM_CODEC), + new Protocols.TypeAndCodec<>(ConfigResponseC2SPacket.TYPE, ConfigResponseC2SPacket.STREAM_CODEC), + new Protocols.TypeAndCodec<>(RollSyncC2SPacket.TYPE, RollSyncC2SPacket.STREAM_CODEC)); + + private final List> s2c = ImmutableList.of( + new Protocols.TypeAndCodec<>(ConfigUpdateAckS2CPacket.TYPE, ConfigUpdateAckS2CPacket.STREAM_CODEC), + new Protocols.TypeAndCodec<>(ConfigSyncS2CPacket.TYPE, ConfigSyncS2CPacket.STREAM_CODEC), + new Protocols.TypeAndCodec<>(RollSyncS2CPacket.TYPE, RollSyncS2CPacket.STREAM_CODEC) + ); + + private ModConfigServer config = DEFAULT; + private boolean configUpdated = false; + + private final Reference2ReferenceMap syncStates = new Reference2ReferenceOpenHashMap<>(); + private final Reference2ReferenceMap scheduledKicks = new Reference2ReferenceOpenHashMap<>(); + public final Reference2BooleanMap isRollingMap = Reference2BooleanMaps.synchronize(new Reference2BooleanOpenHashMap<>()); + public final Reference2FloatMap rollMap = Reference2FloatMaps.synchronize(new Reference2FloatOpenHashMap<>()); + public final Reference2BooleanMap lastIsRollingMap = Reference2BooleanMaps.synchronize(new Reference2BooleanOpenHashMap<>()); + public final Reference2FloatMap lastRollMap = Reference2FloatMaps.synchronize(new Reference2FloatOpenHashMap<>()); + + public static void deinit() { + if (INSTANCE != null) { + INSTANCE = null; + Protocols.unregister(INSTANCE); + } + } + + public static void init( + boolean allowThrusting, + boolean forceEnabled, + boolean forceInstalled, + int installedTimeout, + KineticDamage kineticDamage + ) { + if (INSTANCE == null) { + INSTANCE = new DoABarrelRollProtocol(); + Protocols.register(INSTANCE); + } + INSTANCE.config = new ModConfigServer(allowThrusting, forceEnabled, forceInstalled, installedTimeout, kineticDamage); + INSTANCE.configUpdated = true; + } + + @Override + public String namespace() { + return NAMESPACE; + } + + @Override + public List> c2s() { + return c2s; + } + + @Override + public List> s2c() { + return s2c; + } + + @Override + public void handle(ServerPlayer player, @NotNull LeafCustomPayload payload) { + switch (payload) { + case ConfigUpdateC2SPacket ignored -> + player.connection.send(Protocols.createPacket(new ConfigUpdateAckS2CPacket(PROTOCOL_VERSION, false))); + case ConfigResponseC2SPacket configResponseC2SPacket -> { + var reply = clientReplied(player.connection, configResponseC2SPacket); + if (reply == HandshakeState.RESEND) { + sendHandshake(player); + } + } + case RollSyncC2SPacket rollSyncC2SPacket -> { + var state = getHandshakeState(player.connection); + if (state.state != HandshakeState.ACCEPTED) { + return; + } + var rolling = rollSyncC2SPacket.rolling(); + var roll = rollSyncC2SPacket.roll(); + isRollingMap.put(player.connection, rolling); + if (Float.isInfinite(roll)) { + roll = 0.0F; + } + rollMap.put(player.connection, roll); + } + default -> { + } + } + } + + @Override + public void disconnected(ServerPlayer player) { + final var handler = player.connection; + syncStates.remove(handler); + isRollingMap.removeBoolean(handler); + rollMap.removeFloat(handler); + lastIsRollingMap.removeBoolean(handler); + lastRollMap.removeFloat(handler); + } + + @Override + public void tickTracker(ServerPlayer player) { + if (!isRollingMap.containsKey(player.connection)) { + return; + } + + var isRolling = isRollingMap.getBoolean(player.connection); + var roll = rollMap.getFloat(player.connection); + var lastIsRolling = lastIsRollingMap.getBoolean(player.connection); + var lastRoll = lastRollMap.getFloat(player.connection); + if (isRolling == lastIsRolling && roll == lastRoll) { + return; + } + var payload = new RollSyncS2CPacket(player.getId(), isRolling, roll); + var packet = Protocols.createPacket(payload); + for (ServerPlayerConnection seenBy : player.moonrise$getTrackedEntity().seenBy()) { + if (seenBy instanceof ServerGamePacketListenerImpl conn + && getHandshakeState(conn).state == HandshakeState.ACCEPTED) { + seenBy.send(packet); + } + } + lastIsRollingMap.put(player.connection, isRolling); + lastRollMap.put(player.connection, roll); + } + + @Override + public void tickPlayer(ServerPlayer player) { + if (getHandshakeState(player.connection).state == HandshakeState.NOT_SENT) { + sendHandshake(player); + } + if (!isRollingMap.containsKey(player.connection)) { + return; + } + if (!isRollingMap.getBoolean(player.connection)) { + rollMap.put(player.connection, 0.0F); + } + } + + @Override + public void tickServer(MinecraftServer server) { + var it = scheduledKicks.entrySet().iterator(); + while (it.hasNext()) { + var entry = it.next(); + if (entry.getValue().isDone()) { + it.remove(); + } else { + entry.getValue().tick(); + } + } + + if (configUpdated) { + configUpdated = false; + for (ServerPlayer player : server.getPlayerList().players) { + sendHandshake(player); + } + } + } + + private OptionalInt getSyncTimeout(ModConfigServer config) { + return config.forceInstalled() ? OptionalInt.of(config.installedTimeout()) : OptionalInt.empty(); + } + + private void sendHandshake(ServerPlayer player) { + player.connection.send(Protocols.createPacket(initiateConfigSync(player.connection))); + configSentToClient(player.connection); + } + + private void configSentToClient(ServerGamePacketListenerImpl handler) { + getHandshakeState(handler).state = HandshakeState.SENT; + + OptionalInt timeout = getSyncTimeout(config); + if (timeout.isEmpty()) { + return; + } + scheduledKicks.put(handler, new DelayedRunnable(timeout.getAsInt(), () -> { + if (getHandshakeState(handler).state != HandshakeState.ACCEPTED) { + LOGGER.warn( + "{} did not accept config syncing, config indicates we kick them.", + handler.getPlayer().getName().getString() + ); + handler.disconnect(SYNC_TIMEOUT_MESSAGE, PlayerKickEvent.Cause.PLUGIN); + } + })); + } + + private HandshakeState clientReplied(ServerGamePacketListenerImpl handler, ConfigResponseC2SPacket packet) { + var info = getHandshakeState(handler); + var player = handler.getPlayer(); + + if (info.state == HandshakeState.SENT) { + var protocolVersion = packet.protocolVersion(); + if (protocolVersion < 1 || protocolVersion > PROTOCOL_VERSION) { + LOGGER.warn( + "{} sent unknown protocol version, expected range 1-{}, got {}. Will attempt to proceed anyway.", + player.getName().getString(), + PROTOCOL_VERSION, + protocolVersion + ); + } + + if (protocolVersion == 2 && info.protocolVersion != 2) { + LOGGER.info("{} is using an older protocol version, resending.", player.getName().getString()); + info.state = HandshakeState.RESEND; + } else if (packet.success()) { + LOGGER.info("{} accepted server config.", player.getName().getString()); + info.state = HandshakeState.ACCEPTED; + } else { + LOGGER.warn( + "{} failed to process server config, check client logs find what went wrong.", + player.getName().getString()); + info.state = HandshakeState.FAILED; + } + info.protocolVersion = protocolVersion; + } + + return info.state; + } + + private boolean isLimited(ServerGamePacketListenerImpl net) { + return true; + // return net.getPlayer().getBukkitEntity().hasPermission(DoABarrelRoll.MODID + ".configure"); + } + + private ClientInfo getHandshakeState(ServerGamePacketListenerImpl handler) { + return syncStates.computeIfAbsent(handler, key -> new ClientInfo(HandshakeState.NOT_SENT, PROTOCOL_VERSION, true)); + } + + private ConfigSyncS2CPacket initiateConfigSync(ServerGamePacketListenerImpl handler) { + var isLimited = isLimited(handler); + getHandshakeState(handler).isLimited = isLimited; + return new ConfigSyncS2CPacket(PROTOCOL_VERSION, config, isLimited, isLimited ? DEFAULT : config); + } + + private static class ClientInfo { + private HandshakeState state; + private int protocolVersion; + private boolean isLimited; + + private ClientInfo(HandshakeState state, int protocolVersion, boolean isLimited) { + this.state = state; + this.protocolVersion = protocolVersion; + this.isLimited = isLimited; + } + } + + private static class DelayedRunnable { + private final Runnable runnable; + private final int delay; + private int ticks = 0; + + private DelayedRunnable(int delay, Runnable runnable) { + this.runnable = runnable; + this.delay = delay; + } + + private void tick() { + if (++ticks >= delay) { + runnable.run(); + } + } + + private boolean isDone() { + return ticks >= delay; + } + } + + private enum HandshakeState { + NOT_SENT, + SENT, + ACCEPTED, + FAILED, + RESEND + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/protocol/LeafCustomPayload.java b/leaf-server/src/main/java/org/dreeam/leaf/protocol/LeafCustomPayload.java new file mode 100644 index 00000000..3894dfa8 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/protocol/LeafCustomPayload.java @@ -0,0 +1,11 @@ +package org.dreeam.leaf.protocol; + +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import org.jetbrains.annotations.NotNull; + +public interface LeafCustomPayload extends CustomPacketPayload { + + @NotNull + @Override + Type type(); +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/protocol/Protocol.java b/leaf-server/src/main/java/org/dreeam/leaf/protocol/Protocol.java new file mode 100644 index 00000000..61c5c529 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/protocol/Protocol.java @@ -0,0 +1,27 @@ +package org.dreeam.leaf.protocol; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +interface Protocol { + + String namespace(); + + List> c2s(); + + List> s2c(); + + void tickServer(MinecraftServer server); + + void tickPlayer(ServerPlayer player); + + void tickTracker(ServerPlayer player); + + void disconnected(ServerPlayer conn); + + void handle(ServerPlayer player, @NotNull LeafCustomPayload payload); +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/protocol/Protocols.java b/leaf-server/src/main/java/org/dreeam/leaf/protocol/Protocols.java new file mode 100644 index 00000000..000b6f93 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/protocol/Protocols.java @@ -0,0 +1,100 @@ +package org.dreeam.leaf.protocol; + +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.DiscardedPayload; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Protocols { + + private static final ObjectArrayList PROTOCOLS = new ObjectArrayList<>(); + + static void register(Protocol protocol) { + PROTOCOLS.add(protocol); + } + + static void unregister(Protocol protocol) { + PROTOCOLS.remove(protocol); + } + + public record TypeAndCodec(LeafCustomPayload.Type type, + StreamCodec codec) { + } + + public static void write(B byteBuf, LeafCustomPayload payload) { + for (Protocol protocol : PROTOCOLS) { + if (protocol.namespace().equals(payload.type().id().getNamespace())) { + encode(byteBuf, payload, protocol); + return; + } + } + } + + public static void handle(ServerPlayer player, @NotNull DiscardedPayload payload) { + for (Protocol protocol : PROTOCOLS) { + if (payload.type().id().getNamespace().equals(protocol.namespace())) { + var leafCustomPayload = decode(protocol, payload); + if (leafCustomPayload != null) { + protocol.handle(player, leafCustomPayload); + } + return; + } + } + } + + public static void tickServer(MinecraftServer server) { + for (Protocol protocol : PROTOCOLS) { + protocol.tickServer(server); + } + } + + public static void tickPlayer(ServerPlayer player) { + for (Protocol protocol : PROTOCOLS) { + protocol.tickPlayer(player); + } + } + + public static void tickTracker(ServerPlayer player) { + for (Protocol protocol : PROTOCOLS) { + protocol.tickTracker(player); + } + } + + public static void disconnected(ServerPlayer conn) { + for (Protocol protocol : PROTOCOLS) { + protocol.disconnected(conn); + } + } + + @Contract("_ -> new") + public static @NotNull ClientboundCustomPayloadPacket createPacket(LeafCustomPayload payload) { + return new ClientboundCustomPayloadPacket(payload); + } + + private static void encode(B byteBuf, LeafCustomPayload payload, Protocol protocol) { + for (var codec : protocol.s2c()) { + if (codec.type().id().equals(payload.type().id())) { + byteBuf.writeResourceLocation(payload.type().id()); + //noinspection unchecked,rawtypes + ((StreamCodec) codec.codec()).encode(byteBuf, payload); + return; + } + } + } + + private static @Nullable LeafCustomPayload decode(Protocol protocol, DiscardedPayload payload) { + for (var packet : protocol.c2s()) { + if (packet.type().id().equals(payload.type().id())) { + return packet.codec().decode(new FriendlyByteBuf(Unpooled.wrappedBuffer(payload.data()))); + } + } + return null; + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/FastBitRadixSort.java b/leaf-server/src/main/java/org/dreeam/leaf/util/FastBitRadixSort.java new file mode 100644 index 00000000..598ed57a --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/FastBitRadixSort.java @@ -0,0 +1,112 @@ +package org.dreeam.leaf.util; + +import net.minecraft.world.entity.Entity; + +import java.lang.reflect.Array; // Required for Array.newInstance +import java.util.List; + +public class FastBitRadixSort { + + private static final int SMALL_ARRAY_THRESHOLD = 2; + private Entity[] entityBuffer = new Entity[0]; + private long[] bitsBuffer = new long[0]; + + @SuppressWarnings("unchecked") + public T[] sort(List entities, T_REF referenceEntity, Class entityClass) { + int size = entities.size(); + if (size <= 1) { + T[] resultArray = (T[]) Array.newInstance(entityClass, size); + return entities.toArray(resultArray); + } + + if (this.entityBuffer.length < size) { + this.entityBuffer = new Entity[size]; + this.bitsBuffer = new long[size]; + } + for (int i = 0; i < size; i++) { + this.entityBuffer[i] = entities.get(i); + this.bitsBuffer[i] = Double.doubleToRawLongBits( + referenceEntity.distanceToSqr(entities.get(i)) + ); + } + + fastRadixSort(this.entityBuffer, this.bitsBuffer, 0, size - 1, 62); + + T[] resultArray = (T[]) Array.newInstance(entityClass, size); + for (int i = 0; i < size; i++) { + resultArray[i] = entityClass.cast(this.entityBuffer[i]); + } + return resultArray; + } + + private void fastRadixSort( + Entity[] ents, + long[] bits, + int low, + int high, + int bit + ) { + if (bit < 0 || low >= high) { + return; + } + + if (high - low <= SMALL_ARRAY_THRESHOLD) { + insertionSort(ents, bits, low, high); + return; + } + + int i = low; + int j = high; + final long mask = 1L << bit; + + while (i <= j) { + while (i <= j && (bits[i] & mask) == 0) { + i++; + } + while (i <= j && (bits[j] & mask) != 0) { + j--; + } + if (i < j) { + swap(ents, bits, i++, j--); + } + } + + if (low < j) { + fastRadixSort(ents, bits, low, j, bit - 1); + } + if (i < high) { + fastRadixSort(ents, bits, i, high, bit - 1); + } + } + + private void insertionSort( + Entity[] ents, + long[] bits, + int low, + int high + ) { + for (int i = low + 1; i <= high; i++) { + int j = i; + Entity currentEntity = ents[j]; + long currentBits = bits[j]; + + while (j > low && bits[j - 1] > currentBits) { + ents[j] = ents[j - 1]; + bits[j] = bits[j - 1]; + j--; + } + ents[j] = currentEntity; + bits[j] = currentBits; + } + } + + private void swap(Entity[] ents, long[] bits, int a, int b) { + Entity tempEntity = ents[a]; + ents[a] = ents[b]; + ents[b] = tempEntity; + + long tempBits = bits[a]; + bits[a] = bits[b]; + bits[b] = tempBits; + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/cache/IterateOutwardsCache.java b/leaf-server/src/main/java/org/dreeam/leaf/util/cache/IterateOutwardsCache.java index 2de80d99..bdd59ee7 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/util/cache/IterateOutwardsCache.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/cache/IterateOutwardsCache.java @@ -2,13 +2,12 @@ package org.dreeam.leaf.util.cache; import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongList; +import net.minecraft.core.BlockPos; import java.util.Iterator; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; -import net.minecraft.core.BlockPos; - /** * @author 2No2Name, original implemenation by SuperCoder7979 and Gegy1000 */ diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/cache/LongList2BlockPosMutableIterable.java b/leaf-server/src/main/java/org/dreeam/leaf/util/cache/LongList2BlockPosMutableIterable.java index 53c70ead..5eedd4e7 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/util/cache/LongList2BlockPosMutableIterable.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/cache/LongList2BlockPosMutableIterable.java @@ -2,11 +2,10 @@ package org.dreeam.leaf.util.cache; import it.unimi.dsi.fastutil.longs.LongIterator; import it.unimi.dsi.fastutil.longs.LongList; +import net.minecraft.core.BlockPos; import java.util.Iterator; -import net.minecraft.core.BlockPos; - /** * @author 2No2Name */ diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/list/HashedReferenceList.java b/leaf-server/src/main/java/org/dreeam/leaf/util/list/HashedReferenceList.java new file mode 100644 index 00000000..3a905e30 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/list/HashedReferenceList.java @@ -0,0 +1,303 @@ +package org.dreeam.leaf.util.list; + +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +/** + * A List implementation that maintains a hash-based counter for O(1) element lookup. + * Combines an array-based list for order with a hash map for fast containment checks. + */ +public class HashedReferenceList implements List { + + // The actual ordered storage of elements + private final ReferenceArrayList list = new ReferenceArrayList<>(); + // Tracks occurrence count of each element for O(1) contains checks + private final Reference2IntOpenHashMap counter; + + /** + * Creates a new HashedReferenceList containing all elements from the provided list + * while building a counter map for fast lookups. + */ + public HashedReferenceList(List list) { + this.list.addAll(list); + this.counter = new Reference2IntOpenHashMap<>(); + this.counter.defaultReturnValue(0); + for (T obj : this.list) { + this.counter.addTo(obj, 1); + } + } + + @Override + public int size() { + return this.list.size(); + } + + @Override + public boolean isEmpty() { + return this.list.isEmpty(); + } + + /** + * Checks if an element exists in the list in O(1) time using the counter map. + */ + @Override + public boolean contains(Object o) { + return this.counter.containsKey(o); + } + + @Override + public Iterator iterator() { + return this.listIterator(); + } + + @Override + public Object[] toArray() { + return this.list.toArray(); + } + + @Override + public T1[] toArray(T1 @NotNull [] a) { + return this.list.toArray(a); + } + + /** + * Adds an element and updates the counter map. + */ + @Override + public boolean add(T t) { + this.trackReferenceAdded(t); + return this.list.add(t); + } + + /** + * Removes an element and updates the counter map. + */ + @Override + public boolean remove(Object o) { + this.trackReferenceRemoved(o); + return this.list.remove(o); + } + + /** + * Checks if all elements of the collection exist in this list. + */ + @Override + public boolean containsAll(Collection c) { + for (Object obj : c) { + if (this.counter.containsKey(obj)) continue; + return false; + } + return true; + } + + @Override + public boolean addAll(Collection c) { + for (T obj : c) { + this.trackReferenceAdded(obj); + } + return this.list.addAll(c); + } + + @Override + public boolean addAll(int index, Collection c) { + for (T obj : c) { + this.trackReferenceAdded(obj); + } + return this.list.addAll(index, c); + } + + /** + * Optimizes removal by converting to a hash set for large operations. + */ + @Override + public boolean removeAll(@NotNull Collection c) { + if (this.size() >= 2 && c.size() > 4 && c instanceof List) { + c = new ReferenceOpenHashSet<>(c); + } + this.counter.keySet().removeAll(c); + return this.list.removeAll(c); + } + + @Override + public boolean retainAll(@NotNull Collection c) { + this.counter.keySet().retainAll(c); + return this.list.retainAll(c); + } + + @Override + public void clear() { + this.counter.clear(); + this.list.clear(); + } + + @Override + public T get(int index) { + return this.list.get(index); + } + + /** + * Sets an element at specific index while maintaining accurate counts. + */ + @Override + public T set(int index, T element) { + T prev = this.list.set(index, element); + if (prev != element) { + if (prev != null) { + this.trackReferenceRemoved(prev); + } + this.trackReferenceAdded(element); + } + return prev; + } + + @Override + public void add(int index, T element) { + this.trackReferenceAdded(element); + this.list.add(index, element); + } + + @Override + public T remove(int index) { + T prev = this.list.remove(index); + if (prev != null) { + this.trackReferenceRemoved(prev); + } + return prev; + } + + @Override + public int indexOf(Object o) { + return this.list.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return this.list.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return this.listIterator(0); + } + + /** + * Custom ListIterator implementation that maintains counter consistency. + */ + @Override + public ListIterator listIterator(final int index) { + return new ListIterator<>() { + private final ListIterator inner; + + { + this.inner = HashedReferenceList.this.list.listIterator(index); + } + + @Override + public boolean hasNext() { + return this.inner.hasNext(); + } + + @Override + public T next() { + return this.inner.next(); + } + + @Override + public boolean hasPrevious() { + return this.inner.hasPrevious(); + } + + @Override + public T previous() { + return this.inner.previous(); + } + + @Override + public int nextIndex() { + return this.inner.nextIndex(); + } + + @Override + public int previousIndex() { + return this.inner.previousIndex(); + } + + /** + * Removes the current element and updates counter. + */ + @Override + public void remove() { + int last = this.previousIndex(); + if (last == -1) { + throw new NoSuchElementException(); + } + Object prev = HashedReferenceList.this.get(last); + if (prev != null) { + HashedReferenceList.this.trackReferenceRemoved(prev); + } + this.inner.remove(); + } + + /** + * Sets the current element and updates counter. + */ + @Override + public void set(T t) { + int last = this.previousIndex(); + if (last == -1) { + throw new NoSuchElementException(); + } + Object prev = HashedReferenceList.this.get(last); + if (prev != t) { + if (prev != null) { + HashedReferenceList.this.trackReferenceRemoved(prev); + } + HashedReferenceList.this.trackReferenceAdded(t); + } + this.inner.remove(); + } + + @Override + public void add(T t) { + HashedReferenceList.this.trackReferenceAdded(t); + this.inner.add(t); + } + }; + } + + @Override + public List subList(int fromIndex, int toIndex) { + return this.list.subList(fromIndex, toIndex); + } + + /** + * Increments the reference counter for an added element. + */ + private void trackReferenceAdded(T t) { + this.counter.addTo(t, 1); + } + + /** + * Decrements the reference counter and removes if count reaches 0. + */ + private void trackReferenceRemoved(Object o) { + if (this.counter.addTo((T) o, -1) <= 1) { + this.counter.removeInt(o); + } + } + + /** + * Factory method to create a HashedReferenceList from an existing list. + */ + public static HashedReferenceList wrapper(List list) { + return new HashedReferenceList<>(list); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceArrayMap.java b/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceArrayMap.java new file mode 100644 index 00000000..9d4619c2 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceArrayMap.java @@ -0,0 +1,313 @@ +package org.dreeam.leaf.util.map; + +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.entity.ai.attributes.Attribute; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.AbstractMap.SimpleEntry; + +// fast array backend map with O(1) get & put & remove +public class AttributeInstanceArrayMap implements Map, AttributeInstance>, Cloneable { + + private int size = 0; + private transient AttributeInstance[] a = new AttributeInstance[32]; + private transient KeySet keys; + private transient Values values; + private transient EntrySet entries; + + public AttributeInstanceArrayMap() { + if (BuiltInRegistries.ATTRIBUTE.size() != 32) { + throw new IllegalStateException("Registered custom attribute"); + } + } + + public AttributeInstanceArrayMap(final @NotNull Map, AttributeInstance> m) { + this(); + putAll(m); + } + + private void setByIndex(int index, @Nullable AttributeInstance instance) { + boolean empty = a[index] == null; + if (instance == null) { + if (!empty) { + size--; + a[index] = null; + } + } else { + if (empty) { + size++; + } + a[index] = instance; + } + } + + @Override + public final int size() { + return size; + } + + @Override + public final boolean isEmpty() { + return size == 0; + } + + @Override + public final boolean containsKey(Object key) { + if (key instanceof Holder holder && holder.value() instanceof Attribute attribute) { + int uid = attribute.uid; + return uid >= 0 && uid < a.length && a[uid] != null; + } + return false; + } + + @Override + public final boolean containsValue(Object value) { + for (final AttributeInstance instance : a) { + if (Objects.equals(value, instance)) { + return true; + } + } + return false; + } + + @Override + public final AttributeInstance get(Object key) { + return key instanceof Holder holder && holder.value() instanceof Attribute attribute ? a[attribute.uid] : null; + } + + @Override + public final AttributeInstance put(@NotNull Holder key, AttributeInstance value) { + int uid = key.value().uid; + AttributeInstance prev = a[uid]; + setByIndex(uid, value); + return prev; + } + + @Override + public final AttributeInstance remove(Object key) { + if (!(key instanceof Holder holder) || !(holder.value() instanceof Attribute attribute)) return null; + int uid = attribute.uid; + AttributeInstance prev = a[uid]; + setByIndex(uid, null); + return prev; + } + + @Override + public final void putAll(@NotNull Map, ? extends AttributeInstance> m) { + for (AttributeInstance e : m.values()) { + if (e != null) { + setByIndex(e.getAttribute().value().uid, e); + } + } + } + + @Override + public final void clear() { + Arrays.fill(a, null); + size = 0; + } + + @Override + public final @NotNull Set> keySet() { + if (keys == null) { + keys = new KeySet(); + } + return keys; + } + + @Override + public final @NotNull Collection values() { + if (values == null) { + values = new Values(); + } + return values; + } + + @Override + public final @NotNull Set, AttributeInstance>> entrySet() { + if (entries == null) { + entries = new EntrySet(); + } + return entries; + } + + @Override + public final boolean equals(Object o) { + if (!(o instanceof AttributeInstanceArrayMap that)) return false; + return size == that.size && Arrays.equals(a, that.a); + } + + @Override + public final int hashCode() { + return Arrays.hashCode(a); + } + + @Override + public AttributeInstanceArrayMap clone() { + AttributeInstanceArrayMap c; + try { + c = (AttributeInstanceArrayMap) super.clone(); + } catch (CloneNotSupportedException cantHappen) { + throw new InternalError(); + } + c.a = a.clone(); + c.entries = null; + c.keys = null; + c.values = null; + return c; + } + + private final class KeySet extends AbstractSet> { + @Override + public @NotNull Iterator> iterator() { + return new KeyIterator(); + } + + @Override + public int size() { + return size; + } + + @Override + public boolean contains(Object o) { + return AttributeInstanceArrayMap.this.containsKey(o); + } + } + + private final class KeyIterator implements Iterator> { + private int currentIndex = -1; + private int nextIndex = findNextOccupied(0); + + @Override + public boolean hasNext() { + return nextIndex != -1; + } + + @Override + public Holder next() { + if (!hasNext()) throw new NoSuchElementException(); + currentIndex = nextIndex; + nextIndex = findNextOccupied(nextIndex + 1); + return BuiltInRegistries.ATTRIBUTE.asHolderIdMap().byIdOrThrow(currentIndex); + } + + @Override + public void remove() { + if (currentIndex == -1) throw new IllegalStateException(); + setByIndex(currentIndex, null); + currentIndex = -1; + } + } + + private final class Values extends AbstractCollection { + @Override + public @NotNull Iterator iterator() { + return new ValueIterator(); + } + + @Override + public int size() { + return size; + } + + @Override + public boolean contains(Object o) { + return containsValue(o); + } + } + + private final class ValueIterator implements Iterator { + private int currentIndex = -1; + private int nextIndex = findNextOccupied(0); + + @Override + public boolean hasNext() { + return nextIndex != -1; + } + + @Override + public AttributeInstance next() { + if (!hasNext()) throw new NoSuchElementException(); + currentIndex = nextIndex; + AttributeInstance value = a[nextIndex]; + nextIndex = findNextOccupied(nextIndex + 1); + return value; + } + + @Override + public void remove() { + if (currentIndex == -1) throw new IllegalStateException(); + setByIndex(currentIndex, null); + currentIndex = -1; + } + } + + private final class EntrySet extends AbstractSet, AttributeInstance>> { + @Override + public @NotNull Iterator, AttributeInstance>> iterator() { + return new EntryIterator(); + } + + @Override + public int size() { + return size; + } + + @Override + public boolean contains(Object o) { + if (!(o instanceof Entry e)) { + return false; + } + return Objects.equals(get(e.getKey()), e.getValue()); + } + } + + private final class EntryIterator implements Iterator, AttributeInstance>> { + private int currentIndex = -1; + private int nextIndex = findNextOccupied(0); + + @Override + public boolean hasNext() { + return nextIndex != -1; + } + + @Override + public Entry, AttributeInstance> next() { + if (!hasNext()) throw new NoSuchElementException(); + currentIndex = nextIndex; + Holder key = BuiltInRegistries.ATTRIBUTE.asHolderIdMap().byIdOrThrow(nextIndex); + AttributeInstance value = a[nextIndex]; + nextIndex = findNextOccupied(nextIndex + 1); + return new SimpleEntry<>(key, value) { + @Override + public AttributeInstance setValue(AttributeInstance newValue) { + AttributeInstance old = put(key, newValue); + super.setValue(newValue); + return old; + } + }; + } + + @Override + public void remove() { + if (currentIndex == -1) { + throw new IllegalStateException(); + } + setByIndex(currentIndex, null); + currentIndex = -1; + } + } + + private int findNextOccupied(int start) { + for (int i = start; i < a.length; i++) { + if (a[i] != null) { + return i; + } + } + return -1; + } +} 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 deleted file mode 100644 index 27698be1..00000000 --- a/leaf-server/src/main/java/org/dreeam/leaf/util/map/spottedleaf/LeafConcurrentLong2ReferenceChainedHashTable.java +++ /dev/null @@ -1,2294 +0,0 @@ -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 next = node.getNextPlain(); // Plain read - // Update links with release semantics - if (prev == null) { - setAtIndexRelease(currentTable, index, next); // Removed head - } else { - prev.setNextRelease(next); // Removed middle/end - } - break table_loop; // Removed, exit loop - } - prev = node; - node = node.getNextPlain(); // Plain read - } - // Key not found in chain within lock - break table_loop; - } // End synchronized - } // End table_loop - - if (sizeDelta != 0) { - this.subSize(-sizeDelta); // subSize takes positive count - } - return oldValue; - } - - - /** - * Removes the entry for a key only if currently mapped to a given value. - * - * @param key key with which the specified value is associated - * @param expect value expected to be associated with the specified key - * @return {@code true} if the value was removed - */ - public boolean remove(final long key, final V expect) { - final int hash = getHash(key); - int sizeDelta = 0; - boolean removed = false; - TableEntry[] currentTable = this.table; - - table_loop: - for (;;) { - final int tableLength = currentTable.length; - if (tableLength == 0) return false; - - final int index = hash & (tableLength - 1); - TableEntry head = getAtIndexVolatile(currentTable, index); - - if (head == null) return false; - - if (head.isResizeMarker()) { - currentTable = helpResizeOrGetNextTable(currentTable, head); - continue table_loop; - } - - // Removal needs locking - synchronized (head) { - TableEntry currentHead = getAtIndexVolatile(currentTable, index); - if (currentHead != head || head.isResizeMarker()) { - continue table_loop; // Retry - } - - TableEntry prev = null; - TableEntry node = head; - while (node != null) { - if (node.key == key) { - V currentVal = node.getValuePlain(); // Plain read in lock - if (Objects.equals(currentVal, expect)) { // Safe comparison - removed = true; - sizeDelta = (currentVal != null) ? -1 : 0; // Decrement if actual value - - TableEntry next = node.getNextPlain(); // Plain read - // Update links with release semantics - if (prev == null) { - setAtIndexRelease(currentTable, index, next); - } else { - prev.setNextRelease(next); - } - } else { - removed = false; // Value didn't match - } - break table_loop; // Key processed - } - prev = node; - node = node.getNextPlain(); // Plain read - } - // Key not found in chain within lock - break table_loop; - } // End synchronized - } // End table_loop - - if (sizeDelta != 0) { - this.subSize(-sizeDelta); - } - return removed; - } - - /** - * Removes the entry for the specified key only if its value satisfies the given predicate. - * - * @param key key whose mapping is to be removed from the map - * @param predicate the predicate to apply to the value associated with the key - * @return the value associated with the key before removal if the predicate was satisfied and the entry was removed, - * otherwise {@code null}. - * @throws NullPointerException if the specified predicate is null - */ - public V removeIf(final long key, final Predicate predicate) { - Validate.notNull(predicate, "Predicate may not be null"); - final int hash = getHash(key); - int sizeDelta = 0; - V oldValue = null; - boolean removed = false; - TableEntry[] currentTable = this.table; - - table_loop: - for (;;) { - final int tableLength = currentTable.length; - if (tableLength == 0) return null; - - final int index = hash & (tableLength - 1); - TableEntry head = getAtIndexVolatile(currentTable, index); - - if (head == null) return null; - - if (head.isResizeMarker()) { - currentTable = helpResizeOrGetNextTable(currentTable, head); - continue table_loop; - } - - // Conditional removal needs locking - synchronized (head) { - TableEntry currentHead = getAtIndexVolatile(currentTable, index); - if (currentHead != head || head.isResizeMarker()) { - continue table_loop; // Retry - } - - TableEntry prev = null; - TableEntry node = head; - while (node != null) { - if (node.key == key) { - oldValue = node.getValuePlain(); // Plain read in lock - if (oldValue != null && predicate.test(oldValue)) { // Test non-null value - removed = true; - sizeDelta = -1; - - TableEntry next = node.getNextPlain(); // Plain read - // Update links with release semantics - if (prev == null) { - setAtIndexRelease(currentTable, index, next); - } else { - prev.setNextRelease(next); - } - } else { - removed = false; // Predicate failed or value null - } - break table_loop; // Key processed - } - prev = node; - node = node.getNextPlain(); // Plain read - } - // Key not found in chain within lock - break table_loop; - } // End synchronized - } // End table_loop - - if (sizeDelta != 0) { - this.subSize(-sizeDelta); - } - return removed ? oldValue : null; // Return old value only if removed - } - - // --- Compute Methods --- - - /** - * Attempts to compute a mapping for the specified key and its current mapped value - * (or {@code null} if there is no current mapping). The function is - * applied atomically. - * - * @param key key with which the specified value is to be associated - * @param function the function to compute a value - * @return the new value associated with the specified key, or null if none - * @throws NullPointerException if the specified function is null - */ - public V compute(final long key, final BiLong1Function function) { - Validate.notNull(function, "Function cannot be null"); - final int hash = getHash(key); - int sizeDelta = 0; - V finalValue; - TableEntry[] currentTable = this.table; - - table_loop: - for (;;) { - final int tableLength = currentTable.length; - if (tableLength == 0) { - currentTable = this.table; - continue; - } - - final int index = hash & (tableLength - 1); - TableEntry head = getAtIndexVolatile(currentTable, index); - - // Case 1: Bin is empty. Use placeholder logic. - if (head == null) { - TableEntry placeholder = new TableEntry<>(key, null); // Temp node - V computedValue; - synchronized (placeholder) { // Lock placeholder for atomicity - if (getAtIndexVolatile(currentTable, index) == null) { // Re-check bin - try { - computedValue = function.apply(key, null); // Compute with null old value - } catch (Throwable t) { - ThrowUtil.throwUnchecked(t); - return null; - } - - if (computedValue != null) { - placeholder.setValuePlain(computedValue); // Set value before CAS - // Attempt to insert the computed node - if (compareAndExchangeAtIndexVolatile(currentTable, index, null, placeholder) == null) { - sizeDelta = 1; - finalValue = computedValue; - break table_loop; // Success - } else { - continue table_loop; // CAS failed, retry - } - } else { - finalValue = null; // Computed null, no mapping - break table_loop; - } - } - } // End synchronized(placeholder) - continue table_loop; // Bin changed, retry - } // End Case 1 (head == null) - - // Case 2: Resize marker - if (head.isResizeMarker()) { - currentTable = helpResizeOrGetNextTable(currentTable, head); - continue table_loop; - } - - // Case 3: Bin not empty. Lock head. - synchronized (head) { - TableEntry currentHead = getAtIndexVolatile(currentTable, index); - if (currentHead != head || head.isResizeMarker()) { - continue table_loop; // Retry - } - - TableEntry prev = null; - TableEntry node = head; - while (node != null) { - if (node.key == key) { - // Key found. Compute with existing value. - V oldValue = node.getValuePlain(); // Plain read in lock - V computedValue; - try { - computedValue = function.apply(key, oldValue); - } catch (Throwable t) { - ThrowUtil.throwUnchecked(t); - return null; - } - - if (computedValue != null) { - node.setValueVolatile(computedValue); // Update value (volatile write) - finalValue = computedValue; - sizeDelta = (oldValue == null) ? 1 : 0; // Size change if old was placeholder - } else { - // Remove mapping - finalValue = null; - sizeDelta = (oldValue != null) ? -1 : 0; // Size change only if old was value - TableEntry next = node.getNextPlain(); // Plain read - if (prev == null) setAtIndexRelease(currentTable, index, next); - else prev.setNextRelease(next); - } - break table_loop; // Done - } - prev = node; - node = node.getNextPlain(); // Plain read - } // End while - - // Key not found. Compute with null. - V computedValue; - try { - computedValue = function.apply(key, null); - } catch (Throwable t) { - ThrowUtil.throwUnchecked(t); - return null; - } - - if (computedValue != null) { - // Add new mapping - finalValue = computedValue; - sizeDelta = 1; - TableEntry newNode = new TableEntry<>(key, computedValue); - if (prev != null) prev.setNextRelease(newNode); // Release write - else continue table_loop; // Should not happen - } else { - finalValue = null; - sizeDelta = 0; - } - break table_loop; // Done - } // End synchronized(head) - } // End table_loop - - if (sizeDelta > 0) this.addSize(sizeDelta); - else if (sizeDelta < 0) this.subSize(-sizeDelta); - - return finalValue; - } - - /** - * If the specified key is not already associated with a value, attempts to - * compute its value using the given mapping function and enters it into - * this map unless {@code null}. - * - * @param key key with which the specified value is to be associated - * @param function the function to compute a value - * @return the current (existing or computed) value associated with the specified key, - * or null if the computed value is null - * @throws NullPointerException if the specified function is null - */ - public V computeIfAbsent(final long key, final LongFunction function) { - Validate.notNull(function, "Function cannot be null"); - final int hash = getHash(key); - int sizeDelta = 0; - V finalValue; - TableEntry[] currentTable = this.table; - - table_loop: - for (;;) { - final int tableLength = currentTable.length; - if (tableLength == 0) { - currentTable = this.table; - continue; - } - - final int index = hash & (tableLength - 1); - TableEntry head = getAtIndexVolatile(currentTable, index); - - // Case 1: Bin is empty. Use placeholder. - if (head == null) { - TableEntry placeholder = new TableEntry<>(key, null); - V computedValue; - synchronized (placeholder) { - if (getAtIndexVolatile(currentTable, index) == null) { - try { - computedValue = function.apply(key); - } catch (Throwable t) { - ThrowUtil.throwUnchecked(t); - return null; - } - - if (computedValue != null) { - placeholder.setValuePlain(computedValue); - if (compareAndExchangeAtIndexVolatile(currentTable, index, null, placeholder) == null) { - sizeDelta = 1; - finalValue = computedValue; - break table_loop; // Inserted - } else { - continue table_loop; // CAS failed, retry - } - } else { - finalValue = null; // Computed null - break table_loop; - } - } - } // End synchronized(placeholder) - continue table_loop; // Bin changed, retry - } // End Case 1 - - // Case 2: Resize marker - if (head.isResizeMarker()) { - currentTable = helpResizeOrGetNextTable(currentTable, head); - continue table_loop; - } - - // Case 3: Lock-free check if key already exists with value - TableEntry node = head; - while (node != null) { - if (node.key == key) { - V existingValue = node.getValueVolatile(); // Volatile read - if (existingValue != null) { - return existingValue; // Already present - } - break; // Placeholder found, need lock - } - node = node.getNextVolatile(); - } - - // Case 4: Locking path - synchronized (head) { - TableEntry currentHead = getAtIndexVolatile(currentTable, index); - if (currentHead != head || head.isResizeMarker()) { - continue table_loop; // Retry - } - - TableEntry prev = null; - node = head; - while (node != null) { - if (node.key == key) { - V existingValue = node.getValuePlain(); // Plain read in lock - if (existingValue != null) { - finalValue = existingValue; // Found inside lock - } else { - // Placeholder exists, compute and update - V computedValue; - try { - computedValue = function.apply(key); - } catch (Throwable t) { - ThrowUtil.throwUnchecked(t); - return null; - } - - if (computedValue != null) { - node.setValueVolatile(computedValue); // Volatile write - sizeDelta = 1; - finalValue = computedValue; - } else { - finalValue = null; // Computed null - } - } - break table_loop; // Done - } - prev = node; - node = node.getNextPlain(); // Plain read - } // End while - - // Key not found. Compute and add. - V computedValue; - try { - computedValue = function.apply(key); - } catch (Throwable t) { - ThrowUtil.throwUnchecked(t); - return null; - } - - if (computedValue != null) { - finalValue = computedValue; - sizeDelta = 1; - TableEntry newNode = new TableEntry<>(key, computedValue); - if (prev != null) prev.setNextRelease(newNode); // Release write - else continue table_loop; // Should not happen - } else { - finalValue = null; - sizeDelta = 0; - } - break table_loop; // Done - } // End synchronized(head) - } // End table_loop - - if (sizeDelta > 0) this.addSize(sizeDelta); - return finalValue; - } - - - /** - * If the value for the specified key is present, attempts to compute a new - * mapping given the key and its current mapped value. - * - * @param key key with which the specified value is to be associated - * @param function the function to compute a value - * @return the new value associated with the specified key, or null if none - * @throws NullPointerException if the specified function is null - */ - public V computeIfPresent(final long key, final BiLong1Function function) { - Validate.notNull(function, "Function cannot be null"); - final int hash = getHash(key); - int sizeDelta; - V finalValue; - TableEntry[] currentTable = this.table; - - table_loop: - for (;;) { - final int tableLength = currentTable.length; - if (tableLength == 0) return null; - - final int index = hash & (tableLength - 1); - TableEntry head = getAtIndexVolatile(currentTable, index); - - if (head == null) return null; - - if (head.isResizeMarker()) { - currentTable = helpResizeOrGetNextTable(currentTable, head); - continue table_loop; - } - - // Needs lock for potential removal - synchronized (head) { - TableEntry currentHead = getAtIndexVolatile(currentTable, index); - if (currentHead != head || head.isResizeMarker()) { - continue table_loop; // Retry - } - - TableEntry prev = null; - TableEntry node = head; - while (node != null) { - if (node.key == key) { - V oldValue = node.getValuePlain(); // Plain read in lock - if (oldValue != null) { // Only compute if value present - V computedValue; - try { - computedValue = function.apply(key, oldValue); - } catch (Throwable t) { - ThrowUtil.throwUnchecked(t); - return null; - } - - if (computedValue != null) { - node.setValueVolatile(computedValue); // Update (volatile write) - finalValue = computedValue; - sizeDelta = 0; - } else { - // Remove mapping - finalValue = null; - sizeDelta = -1; - TableEntry next = node.getNextPlain(); // Plain read - if (prev == null) setAtIndexRelease(currentTable, index, next); - else prev.setNextRelease(next); - } - } else { - // Placeholder, treat as absent - finalValue = null; - sizeDelta = 0; - } - break table_loop; // Done - } - prev = node; - node = node.getNextPlain(); // Plain read - } // End while - - // Key not found - finalValue = null; - sizeDelta = 0; - break table_loop; - } // End synchronized(head) - } // End table_loop - - if (sizeDelta < 0) this.subSize(-sizeDelta); - return finalValue; - } - - /** - * If the specified key is not already associated with a value or is - * associated with null, associates it with the given non-null value. - * Otherwise, replaces the associated value with the results of the given - * remapping function, or removes if the result is {@code null}. - * - * @param key key with which the resulting value is to be associated - * @param value the non-null value to be merged with the existing value - * @param function the function to recompute a value if present - * @return the new value associated with the specified key, or null if no - * value is associated with the key - * @throws NullPointerException if the specified value or function is null - */ - public V merge(final long key, final V value, final BiFunction function) { - Validate.notNull(value, "Value cannot be null"); - Validate.notNull(function, "Function cannot be null"); - final int hash = getHash(key); - int sizeDelta; - V finalValue; - TableEntry[] currentTable = this.table; - - table_loop: - for (;;) { - final int tableLength = currentTable.length; - if (tableLength == 0) { - currentTable = this.table; - continue; - } - - final int index = hash & (tableLength - 1); - TableEntry head = getAtIndexVolatile(currentTable, index); - - // Case 1: Bin empty. Insert value. - if (head == null) { - TableEntry newNode = new TableEntry<>(key, value); - if (compareAndExchangeAtIndexVolatile(currentTable, index, null, newNode) == null) { - sizeDelta = 1; - finalValue = value; - break table_loop; // Inserted - } - continue table_loop; // CAS failed, retry - } - - // Case 2: Resize marker - if (head.isResizeMarker()) { - currentTable = helpResizeOrGetNextTable(currentTable, head); - continue table_loop; - } - - // Case 3: Lock head - synchronized (head) { - TableEntry currentHead = getAtIndexVolatile(currentTable, index); - if (currentHead != head || head.isResizeMarker()) { - continue table_loop; // Retry - } - - TableEntry prev = null; - TableEntry node = head; - while (node != null) { - if (node.key == key) { - // Key found. Merge. - V oldValue = node.getValuePlain(); // Plain read in lock - V computedValue; - if (oldValue != null) { - try { - computedValue = function.apply(oldValue, value); // Apply function - } catch (Throwable t) { - ThrowUtil.throwUnchecked(t); - return null; - } - } else { - computedValue = value; // Use provided value if old was placeholder - } - - if (computedValue != null) { - node.setValueVolatile(computedValue); // Update (volatile write) - finalValue = computedValue; - sizeDelta = (oldValue == null) ? 1 : 0; // Size change if old was placeholder - } else { - // Remove mapping - finalValue = null; - sizeDelta = (oldValue != null) ? -1 : 0; // Size change if old was value - TableEntry next = node.getNextPlain(); // Plain read - if (prev == null) setAtIndexRelease(currentTable, index, next); - else prev.setNextRelease(next); - } - break table_loop; // Done - } - prev = node; - node = node.getNextPlain(); // Plain read - } // End while - - // Key not found. Add provided value. - finalValue = value; - sizeDelta = 1; - TableEntry newNode = new TableEntry<>(key, value); - if (prev != null) prev.setNextRelease(newNode); // Release write - else continue table_loop; // Should not happen - break table_loop; // Done - } // End synchronized(head) - } // End table_loop - - if (sizeDelta > 0) this.addSize(sizeDelta); - else if (sizeDelta < 0) this.subSize(-sizeDelta); - - return finalValue; - } - - - /** - * Removes all of the mappings from this map. - * The map will be empty after this call returns. - */ - public void clear() { - long removedCount = 0L; - TableEntry[] currentTable = this.table; // Volatile read - - for (int i = 0; i < currentTable.length; ++i) { - TableEntry head = getAtIndexVolatile(currentTable, i); - - if (head == null || head.isResizeMarker()) continue; - - // Lock bin to clear - synchronized (head) { - TableEntry currentHead = getAtIndexVolatile(currentTable, i); - // Re-check after lock - if (currentHead != head || head.isResizeMarker()) { - continue; // Bin changed, skip - } - - // Count actual mappings and clear bin - TableEntry node = head; - while (node != null) { - if (node.getValuePlain() != null) { // Count non-placeholders - removedCount++; - } - node = node.getNextPlain(); // Plain read in lock - } - // Clear bin head with release semantics - setAtIndexRelease(currentTable, i, null); - } // End synchronized - } // End loop - - if (removedCount > 0) { - this.subSize(removedCount); - } - } - - // --- Iterators and Views --- - - /** - * Returns an iterator over the map entries. - */ - public Iterator> entryIterator() { - return new EntryIterator<>(this); - } - - /** - * Returns an iterator over the map entries (implements Iterable). - */ - @Override - public final Iterator> iterator() { - return this.entryIterator(); - } - - /** - * Returns an iterator over the keys. - */ - public PrimitiveIterator.OfLong keyIterator() { - return new KeyIterator<>(this); - } - - /** - * Returns an iterator over the values. - */ - public Iterator valueIterator() { - return new ValueIterator<>(this); - } - - /** - * Returns a {@link Collection} view of the values contained in this map. - */ - public Collection values() { - Values v = this.values; - return (v != null) ? v : (this.values = new Values<>(this)); - } - - /** - * Returns a {@link Set} view of the mappings contained in this map. - */ - public Set> entrySet() { - EntrySet es = this.entrySet; - return (es != null) ? es : (this.entrySet = new EntrySet<>(this)); - } - - // --- Inner Classes: TableEntry, Iterators, Views --- - - /** - * Represents a key-value mapping entry in the hash table. - * Also used as a resize marker. - */ - public static final class TableEntry { - static final VarHandle TABLE_ENTRY_ARRAY_HANDLE; - private static final VarHandle VALUE_HANDLE; - private static final VarHandle NEXT_HANDLE; - - static { - try { - TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class); - VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class); - NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class); - } catch (Throwable t) { - throw new Error("Failed to initialize TableEntry VarHandles", t); - } - } - - final long key; - private volatile V value; - private volatile TableEntry next; - private final boolean resizeMarker; - - /** - * Constructor for regular map entries. - */ - TableEntry(final long key, final V value) { - this(key, value, false); - } - - /** - * Constructor for potentially creating resize markers. - */ - TableEntry(final long key, final V value, final boolean resize) { - this.key = key; - this.resizeMarker = resize; - this.setValuePlain(value); // Initial plain set - } - - public long getKey() { - return this.key; - } - - public V getValue() { - return getValueVolatile(); - } - - public V setValue(V newValue) { - throw new UnsupportedOperationException("Direct setValue on TableEntry is not supported; use map methods."); - } - - @SuppressWarnings("unchecked") - V getValuePlain() { - return (V) VALUE_HANDLE.get(this); - } - - @SuppressWarnings("unchecked") - V getValueAcquire() { - return (V) VALUE_HANDLE.getAcquire(this); - } - - @SuppressWarnings("unchecked") - V getValueVolatile() { - return (V) VALUE_HANDLE.getVolatile(this); - } - - void setValuePlain(final V value) { - VALUE_HANDLE.set(this, value); - } - - void setValueRelease(final V value) { - VALUE_HANDLE.setRelease(this, value); - } - - void setValueVolatile(final V value) { - VALUE_HANDLE.setVolatile(this, value); - } - - boolean compareAndSetValueVolatile(final V expect, final V update) { - return VALUE_HANDLE.compareAndSet(this, expect, update); - } - - @SuppressWarnings("unchecked") - TableEntry getNextPlain() { - return (TableEntry) NEXT_HANDLE.get(this); - } - - @SuppressWarnings("unchecked") - TableEntry getNextVolatile() { - return (TableEntry) NEXT_HANDLE.getVolatile(this); - } - - void setNextPlain(final TableEntry next) { - NEXT_HANDLE.set(this, next); - } - - void setNextRelease(final TableEntry next) { - NEXT_HANDLE.setRelease(this, next); - } - - void setNextVolatile(final TableEntry next) { - NEXT_HANDLE.setVolatile(this, next); - } - - boolean isResizeMarker() { - return this.resizeMarker; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof TableEntry that)) return false; - return key == that.key && Objects.equals(getValueVolatile(), that.getValueVolatile()); // Use volatile read for value - } - - @Override - public int hashCode() { - return Long.hashCode(key) ^ Objects.hashCode(getValueVolatile()); // Use volatile read for value - } - - @Override - public String toString() { - return key + "=" + getValueVolatile(); // Use volatile read for value - } - } - - /** - * Base class for traversing nodes, handling resizes. - * Note: This iterator implementation is simplified and might not be fully robust against - * rapid concurrent modifications during iteration, particularly multiple resize events. - * It aims for basic correctness in common scenarios. - */ - protected static class NodeIterator { - final LeafConcurrentLong2ReferenceChainedHashTable map; - TableEntry[] currentTable; - TableEntry nextNode; - int nextTableIndex; - TableEntry currentNodeInChain; // Current node within the chain being processed - - NodeIterator(TableEntry[] initialTable, LeafConcurrentLong2ReferenceChainedHashTable map) { - this.map = map; - this.currentTable = initialTable; // Start with the table state at iterator creation - this.nextNode = null; - // Start iteration from the end of the table backwards - this.nextTableIndex = (initialTable == null || initialTable.length == 0) ? -1 : initialTable.length - 1; - this.currentNodeInChain = null; - advance(); // Find the first element - } - - /** - * Advances to find the next valid node (non-null value, non-marker). - * Sets {@code nextNode}. Handles basic traversal and checks for table changes. - */ - final void advance() { - nextNode = null; // Assume no next node initially - - if (currentNodeInChain != null) { - currentNodeInChain = currentNodeInChain.getNextVolatile(); // Move to next in chain - } - - while (nextNode == null) { - if (currentNodeInChain != null) { - // Check if the node is valid (not marker, has value) - if (!currentNodeInChain.isResizeMarker() && currentNodeInChain.getValueVolatile() != null) { - nextNode = currentNodeInChain; // Found a valid node - return; // Exit advance - } - // Node invalid (marker or placeholder), move to the next - currentNodeInChain = currentNodeInChain.getNextVolatile(); - continue; // Check next node in chain - } - - if (nextTableIndex < 0) { - // Check if the underlying table reference changed (indicates resize) - // This is a simplified check; robust iterators might need more complex resize handling - if (this.currentTable != map.table) { - // Table changed, restart iteration from the new table - this.currentTable = map.table; - this.nextTableIndex = (this.currentTable == null || this.currentTable.length == 0) ? -1 : this.currentTable.length - 1; - this.currentNodeInChain = null; - // Retry finding a node from the beginning of the new table - continue; - } - // No table change and all bins checked - return; // Exhausted - } - - if (this.currentTable != null && this.nextTableIndex < this.currentTable.length) { - TableEntry head = getAtIndexVolatile(this.currentTable, this.nextTableIndex--); // Read head and decrement index - - if (head != null && !head.isResizeMarker()) { - // Start traversing this new chain - currentNodeInChain = head; - // Check if the head itself is a valid node - if (currentNodeInChain.getValueVolatile() != null) { - nextNode = currentNodeInChain; - return; // Found valid node (head of bin) - } - // Head is placeholder, continue loop to check next in chain - continue; - } - // Bin was empty or head was marker. Reset chain traversal. - currentNodeInChain = null; - } else { - // Table became null or index out of bounds (shouldn't happen unless table shrinks drastically) - // Force moving to next index to avoid infinite loop - nextTableIndex--; - currentNodeInChain = null; - // Consider checking map.table again here for robustness - if (this.currentTable != map.table) { - // Restart if table changed - this.currentTable = map.table; - this.nextTableIndex = (this.currentTable == null || this.currentTable.length == 0) ? -1 : this.currentTable.length - 1; - continue; - } - } - } // End while (nextNode == null) - } - - - public final boolean hasNext() { - return this.nextNode != null; - } - - /** - * Internal method to get the next node and advance. - */ - final TableEntry findNext() { - TableEntry e = this.nextNode; - if (e == null) { - return null; // Signifies end for internal use - } - advance(); // Prepare for the *next* call - return e; // Return the previously found node - } - } - - /** - * Base class for concrete iterators (Entry, Key, Value). - * Handles remove() and NoSuchElementException. - */ - protected static abstract class BaseIteratorImpl extends NodeIterator implements Iterator { - protected TableEntry lastReturned; // Node returned by last next() call - - protected BaseIteratorImpl(final LeafConcurrentLong2ReferenceChainedHashTable map) { - super(map.table, map); // Initialize NodeIterator - this.lastReturned = null; - } - - /** - * Gets the next node, updates lastReturned, advances iterator. - */ - protected final TableEntry nextNode() throws NoSuchElementException { - TableEntry node = this.nextNode; // Node pre-fetched by advance() - if (node == null) { - throw new NoSuchElementException(); - } - this.lastReturned = node; // Store for remove() - advance(); // Find the *next* node for the subsequent call - return node; // Return the current node - } - - @Override - public void remove() { - TableEntry last = this.lastReturned; - if (last == null) { - throw new IllegalStateException("next() not called or remove() already called"); - } - this.map.remove(last.key); // Delegate removal to map's method - this.lastReturned = null; // Prevent double remove - } - - @Override - public abstract T next() throws NoSuchElementException; // Must be implemented by subclass - - @Override - public void forEachRemaining(final Consumer action) { - Validate.notNull(action, "Action may not be null"); - while (hasNext()) { - action.accept(next()); - } - } - } - - /** - * Iterator over map entries (TableEntry objects). - */ - protected static final class EntryIterator extends BaseIteratorImpl> { - EntryIterator(final LeafConcurrentLong2ReferenceChainedHashTable map) { - super(map); - } - - @Override - public TableEntry next() throws NoSuchElementException { - return nextNode(); - } - } - - /** - * Iterator over map keys (long primitives). - */ - protected static final class KeyIterator extends BaseIteratorImpl implements PrimitiveIterator.OfLong { - KeyIterator(final LeafConcurrentLong2ReferenceChainedHashTable map) { - super(map); - } - - @Override - public long nextLong() throws NoSuchElementException { - return nextNode().key; - } - - @Override - public Long next() throws NoSuchElementException { - return nextLong(); // Autoboxing - } - - @Override - public void forEachRemaining(final LongConsumer action) { - Validate.notNull(action, "Action may not be null"); - while (hasNext()) { - action.accept(nextLong()); - } - } - - @Override - public void forEachRemaining(final Consumer action) { - if (action instanceof LongConsumer) { - forEachRemaining((LongConsumer) action); - } else { - Validate.notNull(action, "Action may not be null"); - while (hasNext()) { - action.accept(nextLong()); // Autoboxing - } - } - } - } - - /** - * Iterator over map values. - */ - protected static final class ValueIterator extends BaseIteratorImpl { - ValueIterator(final LeafConcurrentLong2ReferenceChainedHashTable map) { - super(map); - } - - @Override - public V next() throws NoSuchElementException { - return nextNode().getValueVolatile(); // Volatile read for value - } - } - - // --- Collection Views --- - - /** - * Base class for Collection views (Values, EntrySet). - */ - protected static abstract class BaseCollection implements Collection { - protected final LeafConcurrentLong2ReferenceChainedHashTable map; - - protected BaseCollection(LeafConcurrentLong2ReferenceChainedHashTable map) { - this.map = Validate.notNull(map); - } - - @Override - public int size() { - return map.size(); - } - - @Override - public boolean isEmpty() { - return map.isEmpty(); - } - - @Override - public abstract boolean contains(Object o); // Subclass responsibility - - @Override - public boolean containsAll(Collection c) { - Validate.notNull(c); - for (Object e : c) { - if (!contains(e)) return false; - } - return true; - } - - @Override - public Object[] toArray() { - List list = new ArrayList<>(map.size()); - for (E e : this) list.add(e); // Uses iterator() from subclass - return list.toArray(); - } - - @Override - public T[] toArray(T[] a) { - Validate.notNull(a); - List list = new ArrayList<>(map.size()); - for (E e : this) list.add(e); - return list.toArray(a); - } - - @Override - public void clear() { - map.clear(); - } - - @Override - public boolean add(E e) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean addAll(Collection c) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean remove(Object o) { - Iterator it = iterator(); // Subclass provides iterator - while (it.hasNext()) { - if (Objects.equals(o, it.next())) { - it.remove(); // Use iterator's safe remove - return true; - } - } - return false; - } - - @Override - public boolean removeAll(Collection c) { - Validate.notNull(c); - boolean modified = false; - Iterator it = iterator(); - while (it.hasNext()) { - if (c.contains(it.next())) { - it.remove(); - modified = true; - } - } - return modified; - } - - @Override - public boolean retainAll(Collection c) { - Validate.notNull(c); - boolean modified = false; - Iterator it = iterator(); - while (it.hasNext()) { - if (!c.contains(it.next())) { - it.remove(); - modified = true; - } - } - return modified; - } - - @Override - public boolean removeIf(Predicate filter) { - Validate.notNull(filter); - boolean removed = false; - Iterator it = iterator(); - while (it.hasNext()) { - if (filter.test(it.next())) { - it.remove(); - removed = true; - } - } - return removed; - } - - @Override - public String toString() { - Iterator it = iterator(); - if (!it.hasNext()) return "[]"; - StringBuilder sb = new StringBuilder("["); - for (;;) { - E e = it.next(); - sb.append(e == this ? "(this Collection)" : e); - if (!it.hasNext()) return sb.append(']').toString(); - sb.append(',').append(' '); - } - } - - @Override - public void forEach(Consumer action) { - Validate.notNull(action); - for (E e : this) { // Uses iterator() from subclass - action.accept(e); - } - } - } - - /** - * Collection view for the map's values. - */ - protected static final class Values extends BaseCollection { - Values(LeafConcurrentLong2ReferenceChainedHashTable map) { - super(map); - } - - @Override - public boolean contains(Object o) { - try { - return o != null && map.containsValue((V) o); - } catch (ClassCastException cce) { - return false; - } - } - - @Override - public Iterator iterator() { - return map.valueIterator(); - } - } - - /** - * Set view for the map's entries (TableEntry objects). - */ - protected static final class EntrySet extends BaseCollection> implements Set> { - EntrySet(LeafConcurrentLong2ReferenceChainedHashTable map) { - super(map); - } - - @Override - public boolean contains(Object o) { - if (!(o instanceof LeafConcurrentLong2ReferenceChainedHashTable.TableEntry entry)) return false; - V mappedValue = map.get(entry.getKey()); // Concurrent read - // Use volatile read on entry's value for consistent comparison - return mappedValue != null && Objects.equals(mappedValue, entry.getValueVolatile()); - } - - @Override - public Iterator> iterator() { - return map.entryIterator(); - } - - @Override - public boolean remove(Object o) { - if (!(o instanceof LeafConcurrentLong2ReferenceChainedHashTable.TableEntry entry)) return false; - try { - // Use map's atomic remove(key, value) - // Use volatile read for the expected value - return map.remove(entry.getKey(), (V) entry.getValueVolatile()); - } catch (ClassCastException | NullPointerException cce) { // Handle potential type/null issues - return false; - } - } - - @Override - public int hashCode() { - int h = 0; - for (TableEntry e : this) { - h += e.hashCode(); // Uses entry's hashCode - } - return h; - } - - @Override - public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof Set c)) return false; - if (c.size() != size()) return false; - try { - // relies on containsAll checking entry equality correctly - return containsAll(c); - } catch (ClassCastException | NullPointerException unused) { - return false; - } - } - } -} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/queue/SpscIntQueue.java b/leaf-server/src/main/java/org/dreeam/leaf/util/queue/SpscIntQueue.java index 3368c34e..2aede1fb 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/util/queue/SpscIntQueue.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/queue/SpscIntQueue.java @@ -1,5 +1,7 @@ package org.dreeam.leaf.util.queue; +import java.util.OptionalInt; + /// Lock-free Single Producer Single Consumer Queue public class SpscIntQueue { @@ -33,14 +35,14 @@ public class SpscIntQueue { } - public final int recv() { + public final OptionalInt recv() { final int idx = consumerIdx.getOpaque(); int cachedIdx = producerCachedIdx.getPlain(); if (idx == cachedIdx) { cachedIdx = producerIdx.getAcquire(); producerCachedIdx.setPlain(cachedIdx); if (idx == cachedIdx) { - return Integer.MAX_VALUE; + return OptionalInt.empty(); } } int e = data[idx]; @@ -49,7 +51,7 @@ public class SpscIntQueue { nextIdx = 0; } consumerIdx.setRelease(nextIdx); - return e; + return OptionalInt.of(e); } public final int size() { @@ -57,8 +59,28 @@ public class SpscIntQueue { } static class PaddedAtomicInteger extends java.util.concurrent.atomic.AtomicInteger { + // @formatter:off @SuppressWarnings("unused") - private int i1, i2, i3, i4, i5, i6, i7, i8, - i9, i10, i11, i12, i13, i14, i15; + private byte + i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12, i13, i14, i15, + j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15, + k0, k1, k2, k3, k4, k5, k6, k7, k8, k9, k10, k11, k12, k13, k14, k15, + l0, l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, + + m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, + n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, + o0, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10, o11, o12, o13, o14, o15, + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, + + q0, q1, q2, q3, q4, q5, q6, q7, q8, q9, q10, q11, q12, q13, q14, q15, + r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, + s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, + t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, + + u0, u1, u2, u3, u4, u5, u6, u7, u8, u9, u10, u11, u12, u13, u14, u15, + v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, + w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15, + x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11; + // @formatter:on } } diff --git a/scripts/prepareRelease.sh b/scripts/prepareRelease.sh index 9894d73b..816c5a05 100755 --- a/scripts/prepareRelease.sh +++ b/scripts/prepareRelease.sh @@ -9,6 +9,9 @@ JAR_NAME="leaf-1.21.5" CURRENT_TAG="ver-1.21.5" RELEASE_NOTES="release_notes.md" +# Rename Leaf jar +mv ./leaf-server/build/libs/leaf-paperclip-1.21.5-R0.1-SNAPSHOT-mojmap.jar ./$JAR_NAME-${BUILD_NUMBER}.jar + # Branch name CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) echo "✨Current branch: $CURRENT_BRANCH" diff --git a/todos.md b/todos.md index ae000cf7..f1cd597d 100644 --- a/todos.md +++ b/todos.md @@ -2,7 +2,6 @@ - [ ] Do a benchmark for `Remove streams and iterators from range check`, getEffectiveRange in ChunkMap # Leaf TODOs -- [ ] refactor leaves protocol manager opt and pr it. - [ ] Transfer patch notes to file for Gale and Leaf - [ ] check Dont send useless entity packets - [ ] Use different state to separate different configs reload @@ -12,4 +11,5 @@ - [ ] Update README.md - [ ] Remove stream in Inventory and check new changes - [ ] Check Purpur's Projectile offset config, in BowItem shoot -- [ ] Update from Leaf 1.21.4 (curr commit: `1431eff510a3ac1725ec82d52f5c253b4b7932d9`) +- [ ] Remove Gale's attribute patch +- [ ] Update from Leaf 1.21.4 (curr commit: `a022d84c5b5b52f7e8a62f6bff774d0c23176ed5`)