9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-19 15:09:25 +00:00

Update changes from ver/1.21.4 branch

This commit is contained in:
Dreeam
2025-05-24 14:14:34 +08:00
95 changed files with 4201 additions and 3563 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 <http://aikar.co>
- *
- * 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<CommandSender> 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;
}
-
- /**
- * <p>Sets whether or not the Spigot Timings system should be enabled</p>
@@ -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();
}
-
- /**
- * <p>Sets whether or not the Timings should monitor at Verbose level.</p>
@@ -1459,9 +1440,10 @@ index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..00000000000000000000000000000000
- *
- * @return Enabled or not
- */
- public static boolean isVerboseTimingsEnabled() {
public static boolean isVerboseTimingsEnabled() {
- return verboseEnabled;
- }
+ return false;
}
-
- /**
- * <p>Sets whether or not the Timings should monitor at Verbose level.</p>
@@ -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;
- }
}
-
- /**
- * <p>Gets the interval between Timing History report generation.</p>
@@ -1483,9 +1465,10 @@ index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..00000000000000000000000000000000
- *
- * @return Interval in ticks
- */
- public static int getHistoryInterval() {
public static int getHistoryInterval() {
- return historyInterval;
- }
+ return 0;
}
-
- /**
- * <p>Sets the interval between Timing History report generations.</p>
@@ -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<K, V>(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 <http://aikar.co>
- *
- * 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

View File

@@ -0,0 +1,22 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
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()

View File

@@ -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();

View File

@@ -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<Villager> {
@@ -44,14 +44,34 @@ public class GoToPotentialJobSite extends Behavior<Villager> {
Optional<GlobalPos> 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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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();
}
}
}

View File

@@ -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) {

View File

@@ -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("<detail>") && 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("<detail>", "");
+ 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("<message>")) {
+ messageComponent = io.papermc.paper.command.brigadier.MessageComponentSerializer.message().deserialize(commandSyntaxException.getRawMessage());
+ }
+
+ final String input = commandSyntaxException.getInput();
+ final int cursor = commandSyntaxException.getCursor();
+
+ if (rawMessage.contains("<detail>") && 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("<message>").replacement(messageComponent).build())
+ .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("<detail>").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()));

View File

@@ -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<ServerPlayerConnection> seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl
+ public final Set<ServerPlayerConnection> 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<ServerPlayerConnection> 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<ServerPlayer> 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<AttributeInstance> attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync();
if (!attributesToSync.isEmpty()) {
+ // Leaf start - petal - Multithreaded tracker - send in main thread
+ final Set<AttributeInstance> 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<SynchedEntityData.DataValue<?>> trackedDataValues;
private final Set<net.minecraft.server.network.ServerPlayerConnection> 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<SynchedEntityData.DataValue<?>> 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<AttributeModifier.Operation, Map<ResourceLocation, AttributeModifier>> modifiersByOperation = Maps.newEnumMap(
AttributeModifier.Operation.class
);
- private final Map<ResourceLocation, AttributeModifier> modifierById = new Object2ObjectArrayMap<>();
- private final Map<ResourceLocation, AttributeModifier> permanentModifiers = new Object2ObjectArrayMap<>();
+ // Leaf start - Multithreaded tracker
+ private final boolean multiThreadedTrackingEnabled = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled;
+ private final Map<ResourceLocation, AttributeModifier> modifierById = multiThreadedTrackingEnabled ? it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this) : new Object2ObjectArrayMap<>();
+ private final Map<ResourceLocation, AttributeModifier> 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<NewMinecartBehavior.MinecartStep> lerpSteps = new LinkedList<>();
+ public final List<NewMinecartBehavior.MinecartStep> lerpSteps; // Leaf - Multithreaded tracker
public final List<NewMinecartBehavior.MinecartStep> 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<Holder<Attribute>, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0);
- private final Set<AttributeInstance> attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0);
- private final Set<AttributeInstance> 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<Holder<Attribute>, AttributeInstance> attributes;
+ private final Set<AttributeInstance> attributesToSync;
+ private final Set<AttributeInstance> attributesToUpdate;
+ // Leaf end - Multithreaded tracker
private final AttributeSupplier supplier;
private final java.util.function.Function<Holder<Attribute>, 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

View File

@@ -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<T extends LivingEntity> extends Sensor<T> {
+
+ // 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<LivingEntity> 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<LivingEntity> 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<LivingEntity> 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<LivingEntity> 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<LivingEntity> 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<MemoryModuleType<?>> requires() {

View File

@@ -0,0 +1,173 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
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<net.minecraft.world.level.block.state.BlockState> chunkPacketInfo, BlockEntity[] blockEntities, Map<Heightmap.Types, long[]> 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<ClientGamePacketListener> 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<ClientGamePa
this.lightData = new ClientboundLightUpdatePacketData(pos, lightEngine, skyLight, blockLight);
chunk.getLevel().chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks
}
+ // Leaf start - Async chunk send
+ public ClientboundLevelChunkWithLightPacket(LevelChunk chunk, LevelLightEngine lightEngine, @Nullable BitSet skyLight, @Nullable BitSet blockLight, boolean modifyBlocks, net.minecraft.world.level.block.entity.BlockEntity[] blockEntities, java.util.Map<net.minecraft.world.level.levelgen.Heightmap.Types, long[]> heightmaps) {
+ ChunkPos pos = chunk.getPos();
+ this.x = pos.x;
+ this.z = pos.z;
+ io.papermc.paper.antixray.ChunkPacketInfo<net.minecraft.world.level.block.state.BlockState> 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<net.minecraft.world.level.levelgen.Heightmap.Types, long[]> 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

View File

@@ -1,108 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
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))) {

View File

@@ -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()));

View File

@@ -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) {

View File

@@ -1,110 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
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<UnloadSection> unloadSections = new ConcurrentLong2ReferenceChainedHashTable<>();
+ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<UnloadSection> 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<SectionToUnload> retrieveForAllRegions() {
final List<SectionToUnload> ret = new ArrayList<>();
- for (final Iterator<ConcurrentLong2ReferenceChainedHashTable.TableEntry<UnloadSection>> iterator = this.unloadSections.entryIterator(); iterator.hasNext();) {
- final ConcurrentLong2ReferenceChainedHashTable.TableEntry<UnloadSection> entry = iterator.next();
+ // Leaf start - Replace ConcurrentLong2ReferenceChainedHashTable with custom map
+ for (final Iterator<org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable.TableEntry<UnloadSection>> iterator = this.unloadSections.entryIterator(); iterator.hasNext(); ) {
+ final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable.TableEntry<UnloadSection> 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<SortedArraySet<Ticket>> tickets = new ConcurrentLong2ReferenceChainedHashTable<>();
- private final ConcurrentLong2ReferenceChainedHashTable<Long2IntOpenHashMap> sectionToChunkToExpireCount = new ConcurrentLong2ReferenceChainedHashTable<>();
+ // Leaf start - Replace ConcurrentLong2ReferenceChainedHashTable with custom map
+ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<SortedArraySet<Ticket>> tickets = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>();
+ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<Long2IntOpenHashMap> sectionToChunkToExpireCount = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>();
+ // Leaf end - Replace ConcurrentLong2ReferenceChainedHashTable with custom map
final ChunkUnloadQueue unloadQueue;
- private final ConcurrentLong2ReferenceChainedHashTable<NewChunkHolder> chunkHolders = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(16384, 0.25f);
+ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<NewChunkHolder> 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<ConcurrentLong2ReferenceChainedHashTable.TableEntry<SortedArraySet<Ticket>>> iterator = this.tickets.entryIterator();
+ for (final Iterator<org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable.TableEntry<SortedArraySet<Ticket>>> iterator = this.tickets.entryIterator(); // Leaf - Replace ConcurrentLong2ReferenceChainedHashTable with custom map
iterator.hasNext();) {
- final ConcurrentLong2ReferenceChainedHashTable.TableEntry<SortedArraySet<Ticket>> coordinateTickets = iterator.next();
+ final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable.TableEntry<SortedArraySet<Ticket>> coordinateTickets = iterator.next(); // Leaf - Replace ConcurrentLong2ReferenceChainedHashTable with custom map
final long coordinate = coordinateTickets.getKey();
final SortedArraySet<Ticket> 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<Section> sections;
+ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<Section> 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<ServerChunkTasks> chunkTasks = new ConcurrentLong2ReferenceChainedHashTable<>();
+ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<ServerChunkTasks> 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<net.minecraft.world.level.chunk.LevelChunk> fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>();
+ private final org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<LevelChunk> fullChunks = new org.dreeam.leaf.util.map.spottedleaf.LeafConcurrentLong2ReferenceChainedHashTable<>(); // Leaf - Replace ConcurrentLong2ReferenceChainedHashTable with custom map
public int getFullChunksCount() {
return this.fullChunks.size();
}

View File

@@ -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 {

View File

@@ -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_

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -0,0 +1,85 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: hayanesuru <hayanesuru@outlook.jp>
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<TickTa
GameTestTicker.SINGLETON.tick();
}
+ org.dreeam.leaf.protocol.Protocols.tickServer(this); // Leaf - Protocol core
+
for (int i = 0; i < this.tickables.size(); i++) {
this.tickables.get(i).run();
}
diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java
index 867936866d952c559b6ffa49fdf78acd70a9bab9..b1953701583ba88c524368a52988fea6fb1ab8d2 100644
--- a/net/minecraft/server/level/ServerEntity.java
+++ b/net/minecraft/server/level/ServerEntity.java
@@ -298,6 +298,7 @@ public class ServerEntity {
this.entity.hurtMarked = false;
this.broadcastAndSend(new ClientboundSetEntityMotionPacket(this.entity));
}
+ if (entity instanceof ServerPlayer serverPlayer) org.dreeam.leaf.protocol.Protocols.tickTracker(serverPlayer); // Leaf - Protocol core
}
private Stream<Entity> mountedOrDismounted(List<Entity> 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

View File

@@ -0,0 +1,28 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
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());

View File

@@ -0,0 +1,156 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: hayanesuru <hayanesuru@outlook.jp>
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<Packet<?>> {
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<Packet<?>> {
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<Packet<?>> {
}
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

View File

@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
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 "<null>";
}
};
- private final Map<BlockPos, LevelChunk.RebindableTickingBlockEntityWrapper> tickersInLevel = Maps.newHashMap();
+ private final Map<BlockPos, LevelChunk.RebindableTickingBlockEntityWrapper> 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

View File

@@ -0,0 +1,37 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
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

View File

@@ -0,0 +1,49 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
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();

View File

@@ -0,0 +1,36 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
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<Mob> {
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<MemoryModuleType<?>> requires() {
return ImmutableSet.of(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM);
@@ -25,10 +31,10 @@ public class NearestItemSensor extends Sensor<Mob> {
protected void doTick(ServerLevel level, Mob entity) {
Brain<?> brain = entity.getBrain();
List<ItemEntity> 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;

View File

@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
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) {

View File

@@ -0,0 +1,29 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
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<T extends LivingEntity> extends Sensor<T> {
public void checkForMobsNearby(T sensingEntity) {
Optional<List<LivingEntity>> 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<LivingEntity> entities = memory.get();
+ for (LivingEntity livingEntity : entities) {
+ if (this.mobTest.test(sensingEntity, livingEntity)) {
+ this.mobDetected(sensingEntity);
+ break;
+ }
}
+ // Leaf end - Remove streams in MobSensor
}
}

View File

@@ -0,0 +1,40 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
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<PathfinderMob> {
protected void doTick(ServerLevel level, PathfinderMob entity) {
Brain<?> brain = entity.getBrain();
TargetingConditions targetingConditions = TEMPT_TARGETING.copy().range((float)entity.getAttributeValue(Attributes.TEMPT_RANGE));
- List<Player> 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<net.minecraft.server.level.ServerPlayer> allPlayers = level.players();
+ List<Player> 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(

View File

@@ -0,0 +1,35 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
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<E> { // Paper - non-final
private final List<Weighted<E>> items;
@Nullable
private final WeightedList.Selector<E> selector;
+ private List<Weighted<E>> entryHashList; // Leaf - Use HashedList on WeightedList
protected WeightedList(List<? extends Weighted<E>> items) { // Paper - protected
this.items = List.copyOf(items);
@@ -30,6 +31,7 @@ public class WeightedList<E> { // 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 <E> WeightedList<E> of() {
@@ -80,7 +82,7 @@ public class WeightedList<E> { // Paper - non-final
}
public List<Weighted<E>> unwrap() {
- return this.items;
+ return this.entryHashList; // Leaf - Use HashedList on WeightedList
}
public static <E> Codec<WeightedList<E>> codec(Codec<E> elementCodec) {

View File

@@ -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));

View File

@@ -0,0 +1,86 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: hayanesuru <hayanesuru@outlook.jp>
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<Holder<Attribute>, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0);
+ private final Map<Holder<Attribute>, AttributeInstance> attributes = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(); // Leaf - Optimize AttributeMap
private final Set<AttributeInstance> attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0);
private final Set<AttributeInstance> 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<Holder<Attribute>, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations
+ //private final java.util.function.Function<Holder<Attribute>, 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> 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> 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<Holder<Attribute>, AttributeInstance> instances;
AttributeSupplier(Map<Holder<Attribute>, AttributeInstance> instances) {
- this.instances = instances;
+ this.instances = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(instances); // Leaf - Optimize AttributeMap
}
public AttributeInstance getAttributeInstance(Holder<Attribute> attribute) {

View File

@@ -0,0 +1,25 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: hayanesuru <hayanesuru@outlook.jp>
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

View File

@@ -0,0 +1,27 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: hayanesuru <hayanesuru@outlook.jp>
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<SynchedEntityData.DataValue<?>> list = new ArrayList<>();
+ // Leaf start - Optimize SynchedEntityData#packDirty
+ int cap = 0;
+ for (SynchedEntityData.DataItem<?> dataItem : this.itemsById) {
+ if (dataItem.isDirty()) {
+ cap += 1;
+ }
+ }
+ ArrayList<SynchedEntityData.DataValue<?>> list = new ArrayList<>(cap);
+ // Leaf end - Optimize SynchedEntityData#packDirty
for (SynchedEntityData.DataItem<?> dataItem : this.itemsById) {
if (dataItem.isDirty()) {

View File

@@ -0,0 +1,77 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: wling-art <wlingzhenyu@163.com>
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<TagKey<Fluid>> fluidHeight = new Object2DoubleArrayMap<>(2);
protected boolean wasEyeInWater;
private final Set<TagKey<Fluid>> 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<Fluid> 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<Fluid> 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;

View File

@@ -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<Packet<?>> {
@@ -660,13 +660,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
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<BlockPos> list = new ObjectArrayList<>(set.stream().toList());
+ ObjectArrayList<BlockPos> 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));

View File

@@ -0,0 +1,122 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: hayanesuru <hayanesuru@outlook.jp>
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;

View File

@@ -0,0 +1,63 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: hayanesuru <hayanesuru@outlook.jp>
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<Entity> {
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<Entity> {
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;
}

View File

@@ -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<org.bukkit.entity.Player> 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

View File

@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: hayanesuru <mc@jvavav.com>
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;

View File

@@ -0,0 +1,22 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com>
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<T> {
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;

View File

@@ -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());
}

View File

@@ -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()
);

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,8 @@
package org.dreeam.leaf.async.ai;
import org.jetbrains.annotations.Nullable;
@FunctionalInterface
public interface VWaker {
@Nullable Object wake();
}

View File

@@ -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;
}
}
}

View File

@@ -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");
}

View File

@@ -0,0 +1,8 @@
package org.dreeam.leaf.async.chunk;
public class AsyncChunkSendThread extends Thread {
protected AsyncChunkSendThread(Runnable task) {
super(task);
}
}

View File

@@ -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;

View File

@@ -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);
}}
}
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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<ServerEntity> 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<Entity> 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<ServerEntity> 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<net.minecraft.world.entity.Entity> trackerEntities = entityLookup.trackerEntities;
final ReferenceList<Entity> 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();
}

View File

@@ -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<String> buildSparkHiddenPaths() {
@Nullable String existing = System.getProperty("spark.serverconfigs.hiddenpaths");
List<String> 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() {

View File

@@ -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.

View File

@@ -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);
}
}

View File

@@ -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());

View File

@@ -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 {

View File

@@ -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;

View File

@@ -57,5 +57,8 @@ public class MultithreadedTracker extends ConfigModules {
if (asyncEntityTrackerQueueSize <= 0)
asyncEntityTrackerQueueSize = asyncEntityTrackerMaxThreads * 384;
if (enabled) {
org.dreeam.leaf.async.tracker.MultithreadedTracker.init();
}
}
}

View File

@@ -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() {

View File

@@ -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.",
"随机掉落物品的向上运动."
));
}
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -9,15 +9,19 @@ public class UnknownCommandMessage extends ConfigModules {
return EnumConfigCategory.MISC.getBaseKeyName() + ".message";
}
public static String unknownCommandMessage = "<red><lang:command.unknown.command><newline><detail>";
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: <detail>, shows detail of the unknown command information.""",
placeholder:
<message>, show message of the command exception.
<detail>, shows detail of the command exception.""",
"""
发送未知命令时的消息, 使用 MiniMessage 格式, 设置为 "default" 使用原版消息.
变量: <detail>, 显示未知命令详细信息."""));
变量:
<message>, 显示命令错误所附提示消息.
<detail>, 显示命令错误详细信息."""));
}
}

View File

@@ -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.",
"异步切换连接状态."));
}
}

View File

@@ -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.""",
"""
在仙人掌生长前检查其是否能够存活。"""));
}
}

View File

@@ -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 方法启用缓存,以优化性能并减少内存使用."));
}
}

View File

@@ -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);
}
}

View File

@@ -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 / 更新,而不是整个物品栏中的所有物品。"""));
}
}

View File

@@ -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.""",
"""
是否优化玩家移动处理,跳过不必要的边缘检查并避免冗余的视距更新。"""));
}
}

View File

@@ -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)",
"减少玩家跨区块移动时的区块源更新。"
)
);
}
}

View File

@@ -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 <T extends LeafCustomPayload> LeafCustomPayload.@NotNull Type<T> 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<ConfigResponseC2SPacket> TYPE = createType("config_response");
public static final StreamCodec<FriendlyByteBuf, ConfigResponseC2SPacket> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.INT, ConfigResponseC2SPacket::protocolVersion,
ByteBufCodecs.BOOL, ConfigResponseC2SPacket::success,
ConfigResponseC2SPacket::new
);
@Override
public @NotNull Type<ConfigResponseC2SPacket> type() {
return TYPE;
}
}
public record ConfigSyncS2CPacket(int protocolVersion,
LimitedModConfigServer applicableConfig,
boolean isLimited,
ModConfigServer fullConfig
) implements LeafCustomPayload {
public static final Type<ConfigSyncS2CPacket> TYPE = createType("config_sync");
public static final StreamCodec<FriendlyByteBuf, ConfigSyncS2CPacket> 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<ConfigSyncS2CPacket> type() {
return TYPE;
}
}
public record ConfigUpdateAckS2CPacket(int protocolVersion, boolean success) implements LeafCustomPayload {
public static final Type<ConfigUpdateAckS2CPacket> TYPE = createType("config_update_ack");
public static final StreamCodec<FriendlyByteBuf, ConfigUpdateAckS2CPacket> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.INT, ConfigUpdateAckS2CPacket::protocolVersion,
ByteBufCodecs.BOOL, ConfigUpdateAckS2CPacket::success,
ConfigUpdateAckS2CPacket::new
);
@Override
public @NotNull Type<ConfigUpdateAckS2CPacket> type() {
return TYPE;
}
}
public record ConfigUpdateC2SPacket(int protocolVersion, ModConfigServer config) implements LeafCustomPayload {
public static final Type<ConfigUpdateC2SPacket> TYPE = createType("config_update");
public static final StreamCodec<FriendlyByteBuf, ConfigUpdateC2SPacket> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.INT, ConfigUpdateC2SPacket::protocolVersion,
ModConfigServer.PACKET_CODEC, ConfigUpdateC2SPacket::config,
ConfigUpdateC2SPacket::new
);
@Override
public @NotNull Type<ConfigUpdateC2SPacket> type() {
return TYPE;
}
}
public record RollSyncC2SPacket(boolean rolling, float roll) implements LeafCustomPayload {
public static final Type<RollSyncC2SPacket> TYPE = createType("roll_sync");
public static final StreamCodec<FriendlyByteBuf, RollSyncC2SPacket> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.BOOL, RollSyncC2SPacket::rolling,
ByteBufCodecs.FLOAT, RollSyncC2SPacket::roll,
RollSyncC2SPacket::new
);
@Override
public @NotNull Type<RollSyncC2SPacket> type() {
return TYPE;
}
}
public record RollSyncS2CPacket(int entityId, boolean rolling, float roll) implements LeafCustomPayload {
public static final Type<RollSyncS2CPacket> TYPE = createType("roll_sync");
public static final StreamCodec<FriendlyByteBuf, RollSyncS2CPacket> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.INT, RollSyncS2CPacket::entityId,
ByteBufCodecs.BOOL, RollSyncS2CPacket::rolling,
ByteBufCodecs.FLOAT, RollSyncS2CPacket::roll,
RollSyncS2CPacket::new
);
@Override
public @NotNull Type<RollSyncS2CPacket> type() {
return TYPE;
}
}
public interface LimitedModConfigServer {
boolean allowThrusting();
boolean forceEnabled();
static StreamCodec<ByteBuf, LimitedModConfigServer> 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<ByteBuf, ModConfigServer> 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<ByteBuf, KineticDamage> CODEC =
ByteBufCodecs.STRING_UTF8.map(KineticDamage::valueOf, KineticDamage::name);
}
}

View File

@@ -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<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> 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<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> 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<ServerGamePacketListenerImpl, ClientInfo> syncStates = new Reference2ReferenceOpenHashMap<>();
private final Reference2ReferenceMap<ServerGamePacketListenerImpl, DelayedRunnable> scheduledKicks = new Reference2ReferenceOpenHashMap<>();
public final Reference2BooleanMap<ServerGamePacketListenerImpl> isRollingMap = Reference2BooleanMaps.synchronize(new Reference2BooleanOpenHashMap<>());
public final Reference2FloatMap<ServerGamePacketListenerImpl> rollMap = Reference2FloatMaps.synchronize(new Reference2FloatOpenHashMap<>());
public final Reference2BooleanMap<ServerGamePacketListenerImpl> lastIsRollingMap = Reference2BooleanMaps.synchronize(new Reference2BooleanOpenHashMap<>());
public final Reference2FloatMap<ServerGamePacketListenerImpl> 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<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> c2s() {
return c2s;
}
@Override
public List<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> 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
}
}

View File

@@ -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<? extends LeafCustomPayload> type();
}

View File

@@ -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<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> c2s();
List<Protocols.TypeAndCodec<FriendlyByteBuf, ? extends LeafCustomPayload>> s2c();
void tickServer(MinecraftServer server);
void tickPlayer(ServerPlayer player);
void tickTracker(ServerPlayer player);
void disconnected(ServerPlayer conn);
void handle(ServerPlayer player, @NotNull LeafCustomPayload payload);
}

View File

@@ -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<Protocol> PROTOCOLS = new ObjectArrayList<>();
static void register(Protocol protocol) {
PROTOCOLS.add(protocol);
}
static void unregister(Protocol protocol) {
PROTOCOLS.remove(protocol);
}
public record TypeAndCodec<B extends FriendlyByteBuf, T extends LeafCustomPayload>(LeafCustomPayload.Type<T> type,
StreamCodec<B, T> codec) {
}
public static <B extends FriendlyByteBuf> 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 <B extends FriendlyByteBuf> 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;
}
}

View File

@@ -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 extends Entity, T_REF extends Entity> T[] sort(List<T> entities, T_REF referenceEntity, Class<T> 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;
}
}

View File

@@ -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
*/

View File

@@ -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
*/

View File

@@ -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<T> implements List<T> {
// The actual ordered storage of elements
private final ReferenceArrayList<T> list = new ReferenceArrayList<>();
// Tracks occurrence count of each element for O(1) contains checks
private final Reference2IntOpenHashMap<T> counter;
/**
* Creates a new HashedReferenceList containing all elements from the provided list
* while building a counter map for fast lookups.
*/
public HashedReferenceList(List<T> 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<T> iterator() {
return this.listIterator();
}
@Override
public Object[] toArray() {
return this.list.toArray();
}
@Override
public <T1> 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<? extends T> c) {
for (T obj : c) {
this.trackReferenceAdded(obj);
}
return this.list.addAll(c);
}
@Override
public boolean addAll(int index, Collection<? extends T> 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<T> listIterator() {
return this.listIterator(0);
}
/**
* Custom ListIterator implementation that maintains counter consistency.
*/
@Override
public ListIterator<T> listIterator(final int index) {
return new ListIterator<>() {
private final ListIterator<T> 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<T> 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 <T> HashedReferenceList<T> wrapper(List<T> list) {
return new HashedReferenceList<>(list);
}
}

View File

@@ -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<Holder<Attribute>, 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<Holder<Attribute>, 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<Attribute> 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 Holder<Attribute>, ? 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<Holder<Attribute>> keySet() {
if (keys == null) {
keys = new KeySet();
}
return keys;
}
@Override
public final @NotNull Collection<AttributeInstance> values() {
if (values == null) {
values = new Values();
}
return values;
}
@Override
public final @NotNull Set<Entry<Holder<Attribute>, 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<Holder<Attribute>> {
@Override
public @NotNull Iterator<Holder<Attribute>> 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<Holder<Attribute>> {
private int currentIndex = -1;
private int nextIndex = findNextOccupied(0);
@Override
public boolean hasNext() {
return nextIndex != -1;
}
@Override
public Holder<Attribute> 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<AttributeInstance> {
@Override
public @NotNull Iterator<AttributeInstance> 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<AttributeInstance> {
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<Entry<Holder<Attribute>, AttributeInstance>> {
@Override
public @NotNull Iterator<Entry<Holder<Attribute>, 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<Entry<Holder<Attribute>, AttributeInstance>> {
private int currentIndex = -1;
private int nextIndex = findNextOccupied(0);
@Override
public boolean hasNext() {
return nextIndex != -1;
}
@Override
public Entry<Holder<Attribute>, AttributeInstance> next() {
if (!hasNext()) throw new NoSuchElementException();
currentIndex = nextIndex;
Holder<Attribute> 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;
}
}

View File

@@ -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
}
}

View File

@@ -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"

View File

@@ -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`)