From c12312bc336a7e6c197170f1844bfa8e0835eee4 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Thu, 17 Apr 2025 03:44:13 -0400 Subject: [PATCH] Update changes from ver/1.21.4 branch --- ...-writeLongArray-during-chunk-loading.patch | 56 +++ ...-writeLongArray-during-chunk-loading.patch | 46 --- ...SparklyPaper-Parallel-world-ticking.patch} | 139 +++++++- ...n-t-block-main-thread-in-Connection-.patch | 0 ...0140-Configurable-connection-message.patch | 14 +- .../features/0170-Multithreaded-Tracker.patch | 23 +- ...0171-Nitori-Async-playerdata-saving.patch} | 129 ++++--- ...y-to-pre-populate-the-size-of-ticki.patch} | 0 ...pre-filtered-ticking-chunks-list-as.patch} | 0 ...ze-AABB.patch => 0210-Optimize-AABB.patch} | 0 ...1-Improve-sorting-in-SortedArraySet.patch} | 0 ... 0212-Make-removeIf-slightly-faster.patch} | 0 ...atch => 0213-Optimize-LinearPalette.patch} | 0 ...214-Slightly-optimized-VarInt-write.patch} | 0 ...te-ClientboundLightUpdatePacketData.patch} | 0 ...imizations-on-SerializableChunkData.patch} | 0 ...h => 0217-Rework-ChunkHolderManager.patch} | 0 ....patch => 0218-Optimize-chunkUnload.patch} | 2 +- ...g.patch => 0219-Async-chunk-sending.patch} | 0 ...atch => 0220-Spawner-Configurations.patch} | 0 ...lled-Projectile-Events-still-consum.patch} | 0 ...ndInteract-and-NearestVisibleLiving.patch} | 0 ...emove-streams-on-InsideBrownianWalk.patch} | 0 ...=> 0224-Use-BFS-on-getSlopeDistance.patch} | 60 ++-- ...r-PR-Throttle-failed-spawn-attempts.patch} | 0 ...BlockEntity-ticking-isRemoved-check.patch} | 0 ...7-Raytrace-AntiXray-SDK-integration.patch} | 0 ...timize-addOrUpdateTransientModifier.patch} | 0 ... => 0229-Optimize-ContextMap.create.patch} | 0 ...Micro-optimizations-for-random-tick.patch} | 2 +- ...n-updateConnectedPlayersWithinRange.patch} | 0 ...32-Remove-streams-on-PlayerDetector.patch} | 0 .../features/0233-Async-Block-Finding.patch | 183 ++++++++++ .../features/0234-Async-Block-Finding.patch | 126 ------- ...se-direct-iteration-on-Sensing.tick.patch} | 0 ...5-Optimise-non-flush-packet-sending.patch} | 4 +- ...unk-retrieving-in-entity-fluid-push.patch} | 0 .../features/0237-Async-Target-Finding.patch | 319 ++++++++++++++++++ ...8-Null-handling-on-MultifaceSpreader.patch | 26 ++ .../features/0239-More-virtual-threads.patch | 112 ++++++ .../0048-PlayerInventoryOverflowEvent.patch | 9 +- .../0050-Async-playerdata-saving.patch | 41 +-- .../leaf/async/AsyncPlayerDataSaving.java | 9 +- .../modules/async/AsyncBlockFinding.java | 3 - .../config/modules/async/AsyncChunkSend.java | 1 - .../modules/async/AsyncTargetFinding.java | 32 ++ .../config/modules/opt/VT4DownloadPool.java | 21 ++ .../modules/opt/VT4ProfileExecutor.java | 21 ++ .../util/map/StringCanonizingOpenHashMap.java | 11 +- todos.md | 2 +- 50 files changed, 1082 insertions(+), 309 deletions(-) create mode 100644 leaf-archived-patches/unapplied/server/minecraft-patches/features/0126-Bulk-writes-to-writeLongArray-during-chunk-loading.patch delete mode 100644 leaf-archived-patches/unapplied/server/minecraft-patches/features/0127-Bulk-writes-to-writeLongArray-during-chunk-loading.patch rename leaf-archived-patches/unapplied/server/minecraft-patches/features/{0139-SparklyPaper-Parallel-world-ticking.patch => 0138-SparklyPaper-Parallel-world-ticking.patch} (92%) rename {leaf-server/minecraft-patches/features => leaf-archived-patches/work/server}/0208-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch (100%) rename leaf-server/minecraft-patches/features/{0171-Nitori-Async-playerdata-Save.patch => 0171-Nitori-Async-playerdata-saving.patch} (68%) rename leaf-server/minecraft-patches/features/{0209-Use-ensureCapacity-to-pre-populate-the-size-of-ticki.patch => 0208-Use-ensureCapacity-to-pre-populate-the-size-of-ticki.patch} (100%) rename leaf-server/minecraft-patches/features/{0210-Directly-use-the-pre-filtered-ticking-chunks-list-as.patch => 0209-Directly-use-the-pre-filtered-ticking-chunks-list-as.patch} (100%) rename leaf-server/minecraft-patches/features/{0211-Optimize-AABB.patch => 0210-Optimize-AABB.patch} (100%) rename leaf-server/minecraft-patches/features/{0212-Improve-sorting-in-SortedArraySet.patch => 0211-Improve-sorting-in-SortedArraySet.patch} (100%) rename leaf-server/minecraft-patches/features/{0213-Make-removeIf-slightly-faster.patch => 0212-Make-removeIf-slightly-faster.patch} (100%) rename leaf-server/minecraft-patches/features/{0214-Optimize-LinearPalette.patch => 0213-Optimize-LinearPalette.patch} (100%) rename leaf-server/minecraft-patches/features/{0215-Slightly-optimized-VarInt-write.patch => 0214-Slightly-optimized-VarInt-write.patch} (100%) rename leaf-server/minecraft-patches/features/{0216-Rewrite-ClientboundLightUpdatePacketData.patch => 0215-Rewrite-ClientboundLightUpdatePacketData.patch} (100%) rename leaf-server/minecraft-patches/features/{0217-Some-Optimizations-on-SerializableChunkData.patch => 0216-Some-Optimizations-on-SerializableChunkData.patch} (100%) rename leaf-server/minecraft-patches/features/{0218-Rework-ChunkHolderManager.patch => 0217-Rework-ChunkHolderManager.patch} (100%) rename leaf-server/minecraft-patches/features/{0219-Optimize-chunkUnload.patch => 0218-Optimize-chunkUnload.patch} (99%) rename leaf-server/minecraft-patches/features/{0220-Async-chunk-sending.patch => 0219-Async-chunk-sending.patch} (100%) rename leaf-server/minecraft-patches/features/{0221-Spawner-Configurations.patch => 0220-Spawner-Configurations.patch} (100%) rename leaf-server/minecraft-patches/features/{0222-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch => 0221-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch} (100%) rename leaf-server/minecraft-patches/features/{0223-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch => 0222-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch} (100%) rename leaf-server/minecraft-patches/features/{0224-Remove-streams-on-InsideBrownianWalk.patch => 0223-Remove-streams-on-InsideBrownianWalk.patch} (100%) rename leaf-server/minecraft-patches/features/{0225-Use-BFS-on-getSlopeDistance.patch => 0224-Use-BFS-on-getSlopeDistance.patch} (85%) rename leaf-server/minecraft-patches/features/{0226-Paper-PR-Throttle-failed-spawn-attempts.patch => 0225-Paper-PR-Throttle-failed-spawn-attempts.patch} (100%) rename leaf-server/minecraft-patches/features/{0227-Improve-BlockEntity-ticking-isRemoved-check.patch => 0226-Improve-BlockEntity-ticking-isRemoved-check.patch} (100%) rename leaf-server/minecraft-patches/features/{0228-Raytrace-AntiXray-SDK-integration.patch => 0227-Raytrace-AntiXray-SDK-integration.patch} (100%) rename leaf-server/minecraft-patches/features/{0229-Optimize-addOrUpdateTransientModifier.patch => 0228-Optimize-addOrUpdateTransientModifier.patch} (100%) rename leaf-server/minecraft-patches/features/{0230-Optimize-ContextMap.create.patch => 0229-Optimize-ContextMap.create.patch} (100%) rename leaf-server/minecraft-patches/features/{0231-Micro-optimizations-for-random-tick.patch => 0230-Micro-optimizations-for-random-tick.patch} (97%) rename leaf-server/minecraft-patches/features/{0232-Remove-streams-on-updateConnectedPlayersWithinRange.patch => 0231-Remove-streams-on-updateConnectedPlayersWithinRange.patch} (100%) rename leaf-server/minecraft-patches/features/{0233-Remove-streams-on-PlayerDetector.patch => 0232-Remove-streams-on-PlayerDetector.patch} (100%) create mode 100644 leaf-server/minecraft-patches/features/0233-Async-Block-Finding.patch delete mode 100644 leaf-server/minecraft-patches/features/0234-Async-Block-Finding.patch rename leaf-server/minecraft-patches/features/{0235-Use-direct-iteration-on-Sensing.tick.patch => 0234-Use-direct-iteration-on-Sensing.tick.patch} (100%) rename leaf-server/minecraft-patches/features/{0236-Optimise-non-flush-packet-sending.patch => 0235-Optimise-non-flush-packet-sending.patch} (94%) rename leaf-server/minecraft-patches/features/{0237-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch => 0236-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch} (100%) create mode 100644 leaf-server/minecraft-patches/features/0237-Async-Target-Finding.patch create mode 100644 leaf-server/minecraft-patches/features/0238-Null-handling-on-MultifaceSpreader.patch create mode 100644 leaf-server/minecraft-patches/features/0239-More-virtual-threads.patch create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4DownloadPool.java create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4ProfileExecutor.java diff --git a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0126-Bulk-writes-to-writeLongArray-during-chunk-loading.patch b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0126-Bulk-writes-to-writeLongArray-during-chunk-loading.patch new file mode 100644 index 00000000..8ac54e00 --- /dev/null +++ b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0126-Bulk-writes-to-writeLongArray-during-chunk-loading.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Fri, 21 Feb 2025 15:06:55 +0100 +Subject: [PATCH] Bulk writes to writeLongArray during chunk loading + + +diff --git a/net/minecraft/network/FriendlyByteBuf.java b/net/minecraft/network/FriendlyByteBuf.java +index abb0141426fd716e79a947b9498a8351daa342fc..6971f93c3f6008f2c2f99fc52e2a3058fd8b7659 100644 +--- a/net/minecraft/network/FriendlyByteBuf.java ++++ b/net/minecraft/network/FriendlyByteBuf.java +@@ -341,10 +341,43 @@ public class FriendlyByteBuf extends ByteBuf { + + public FriendlyByteBuf writeLongArray(long[] array) { + this.writeVarInt(array.length); ++ // Leaf start - Bulk writes to writeLongArray during chunk loading ++ if (array.length == 0) { ++ return this; ++ } ++ int neededBytes = array.length * Long.BYTES; ++ int maxWritableBytes = this.source.maxWritableBytes(); ++ ++ if (maxWritableBytes >= neededBytes) { ++ this.source.ensureWritable(neededBytes); ++ int writerIndex = this.source.writerIndex(); ++ ++ if (this.source.hasArray()) { ++ byte[] dest = this.source.array(); ++ int offset = this.source.arrayOffset() + writerIndex; ++ ++ ByteBuffer buf = ByteBuffer.wrap(dest, offset, neededBytes).order(this.source.order()); ++ buf.asLongBuffer().put(array); + +- for (long l : array) { +- this.writeLong(l); ++ this.source.writerIndex(writerIndex + neededBytes); ++ } else if (this.source.nioBufferCount() > 0) { ++ ByteBuffer nioBuf = this.source.nioBuffer(writerIndex, neededBytes); ++ nioBuf.asLongBuffer().put(array); ++ this.source.writerIndex(writerIndex + neededBytes); ++ } else { ++ ByteBuffer temp = ByteBuffer.allocate(neededBytes).order(this.source.order()); ++ temp.asLongBuffer().put(array); ++ temp.rewind(); ++ this.source.writeBytes(temp); ++ } ++ } else { ++ // Not enough space even at max capacity, use traditional approach ++ // which will write each element individually (and handle growing the buffer as needed) ++ for (long l : array) { ++ this.writeLong(l); ++ } + } ++ // Leaf end - Bulk writes to writeLongArray during chunk loading + + return this; + } diff --git a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0127-Bulk-writes-to-writeLongArray-during-chunk-loading.patch b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0127-Bulk-writes-to-writeLongArray-during-chunk-loading.patch deleted file mode 100644 index 947081e1..00000000 --- a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0127-Bulk-writes-to-writeLongArray-during-chunk-loading.patch +++ /dev/null @@ -1,46 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Fri, 21 Feb 2025 15:06:55 +0100 -Subject: [PATCH] Bulk writes to writeLongArray during chunk loading - - -diff --git a/net/minecraft/network/FriendlyByteBuf.java b/net/minecraft/network/FriendlyByteBuf.java -index abb0141426fd716e79a947b9498a8351daa342fc..5353b8331cf9c428ab7ffba6ebeac4fa323af456 100644 ---- a/net/minecraft/network/FriendlyByteBuf.java -+++ b/net/minecraft/network/FriendlyByteBuf.java -@@ -341,10 +341,33 @@ public class FriendlyByteBuf extends ByteBuf { - - public FriendlyByteBuf writeLongArray(long[] array) { - this.writeVarInt(array.length); -+ // Leaf start - Bulk writes to writeLongArray during chunk loading -+ if (array.length == 0) { -+ return this; -+ } -+ -+ this.source.ensureWritable(array.length * Long.BYTES); -+ int writerIndex = this.source.writerIndex(); -+ -+ if (this.source.hasArray()) { -+ byte[] dest = this.source.array(); -+ int offset = this.source.arrayOffset() + writerIndex; - -- for (long l : array) { -- this.writeLong(l); -+ ByteBuffer buf = ByteBuffer.wrap(dest, offset, array.length * Long.BYTES).order(this.source.order()); -+ buf.asLongBuffer().put(array); -+ -+ this.source.writerIndex(writerIndex + array.length * Long.BYTES); -+ } else if (this.source.nioBufferCount() > 0) { -+ ByteBuffer nioBuf = this.source.nioBuffer(writerIndex, array.length * Long.BYTES); -+ nioBuf.asLongBuffer().put(array); -+ this.source.writerIndex(writerIndex + array.length * Long.BYTES); -+ } else { -+ ByteBuffer temp = ByteBuffer.allocate(array.length * Long.BYTES).order(this.source.order()); -+ temp.asLongBuffer().put(array); -+ temp.rewind(); -+ this.source.writeBytes(temp); - } -+ // Leaf end - Bulk writes to writeLongArray during chunk loading - - return this; - } diff --git a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0139-SparklyPaper-Parallel-world-ticking.patch b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0138-SparklyPaper-Parallel-world-ticking.patch similarity index 92% rename from leaf-archived-patches/unapplied/server/minecraft-patches/features/0139-SparklyPaper-Parallel-world-ticking.patch rename to leaf-archived-patches/unapplied/server/minecraft-patches/features/0138-SparklyPaper-Parallel-world-ticking.patch index ba6ff443..698bc071 100644 --- a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0139-SparklyPaper-Parallel-world-ticking.patch +++ b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0138-SparklyPaper-Parallel-world-ticking.patch @@ -423,6 +423,133 @@ index c50a301a0c2365c2052aefc6a23fcf6fa82e1b9d..ac751d460ae0c8dbb858c4047c459a11 this.levels = Collections.unmodifiableMap(newLevels); } // CraftBukkit end +diff --git a/net/minecraft/server/PlayerAdvancements.java b/net/minecraft/server/PlayerAdvancements.java +index d2159a747fe42aa95cfc6bca0e55e3f4485847bb..abe7ffd48766c48fab091947f34db436b3c883d0 100644 +--- a/net/minecraft/server/PlayerAdvancements.java ++++ b/net/minecraft/server/PlayerAdvancements.java +@@ -53,8 +53,11 @@ public class PlayerAdvancements { + private AdvancementTree tree; + private final Map progress = new LinkedHashMap<>(); + private final Set visible = new HashSet<>(); +- private final Set progressChanged = new HashSet<>(); +- private final Set rootsToUpdate = new HashSet<>(); ++ // Leaf start - SparklyPaper - parallel world ticking ++ private final Set progressChanged = new HashSet<>(); // Used when PWT is disabled ++ private final Set progressChangedConcurrent = java.util.concurrent.ConcurrentHashMap.newKeySet(); // Used when PWT is enabled ++ private final Set rootsToUpdate = new HashSet<>(); // Always managed on player tick thread ++ // Leaf end - SparklyPaper - parallel world ticking + private ServerPlayer player; + @Nullable + private AdvancementHolder lastSelectedTab; +@@ -88,6 +91,7 @@ public class PlayerAdvancements { + this.visible.clear(); + this.rootsToUpdate.clear(); + this.progressChanged.clear(); ++ this.progressChangedConcurrent.clear(); // Leaf - SparklyPaper - parallel world ticking fix - Also clear concurrent set on reload + this.isFirstPacket = true; + this.lastSelectedTab = null; + this.tree = manager.tree(); +@@ -151,7 +155,7 @@ public class PlayerAdvancements { + if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.ignoredAdvancements) LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", path, this.playerSavePath); // Gale - Purpur - do not log ignored advancements + } else { + this.startProgress(advancementHolder, progress); +- this.progressChanged.add(advancementHolder); ++ this.progressChanged.add(advancementHolder); // Leaf - SparklyPaper - parallel world ticking fix - Always add to non-concurrent set during load, flushDirty will handle sync + this.markForVisibilityUpdate(advancementHolder); + } + }); +@@ -183,10 +187,12 @@ public class PlayerAdvancements { + return false; + } + // Paper end - Add PlayerAdvancementCriterionGrantEvent +- this.unregisterListeners(advancement); +- this.progressChanged.add(advancement); +- flag = true; +- if (!isDone && orStartProgress.isDone()) { ++ // Leaf start - SparklyPaper - parallel world ticking ++ this.unregisterListeners(advancement); // Must unregister criteria listeners ++ (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? this.progressChangedConcurrent : this.progressChanged).add(advancement); ++ flag = true; // Mark progress changed ++ if (!isDone && orStartProgress.isDone()) { // If the advancement was just completed ++ // Leaf end - SparklyPaper - parallel world ticking + // Paper start - Add Adventure message to PlayerAdvancementDoneEvent + final net.kyori.adventure.text.Component message = advancement.value().display().flatMap(info -> { + return java.util.Optional.ofNullable( +@@ -220,12 +226,14 @@ public class PlayerAdvancements { + AdvancementProgress orStartProgress = this.getOrStartProgress(advancement); + boolean isDone = orStartProgress.isDone(); + if (orStartProgress.revokeProgress(criterionKey)) { +- this.registerListeners(advancement); +- this.progressChanged.add(advancement); ++ // Leaf start - SparklyPaper - parallel world ticking ++ this.registerListeners(advancement); // Re-register listeners if it's no longer done ++ (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? this.progressChangedConcurrent : this.progressChanged).add(advancement); ++ // Leaf end - SparklyPaper - parallel world ticking + flag = true; + } + +- if (isDone && !orStartProgress.isDone()) { ++ if (isDone && !orStartProgress.isDone()) { // Leaf - SparklyPaper - parallel world ticking - If the advancement was just un-completed + this.markForVisibilityUpdate(advancement); + } + +@@ -271,7 +279,10 @@ public class PlayerAdvancements { + } + + public void flushDirty(ServerPlayer serverPlayer) { +- if (this.isFirstPacket || !this.rootsToUpdate.isEmpty() || !this.progressChanged.isEmpty()) { ++ // Leaf start - SparklyPaper - parallel world ticking ++ final boolean useConcurrent = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled; ++ final Set relevantProgressSet = useConcurrent ? this.progressChangedConcurrent : this.progressChanged; ++ if (this.isFirstPacket || !this.rootsToUpdate.isEmpty() || !relevantProgressSet.isEmpty()) { + Map map = new HashMap<>(); + Set set = new java.util.TreeSet<>(java.util.Comparator.comparing(adv -> adv.id().toString())); // Paper - Changed from HashSet to TreeSet ordered alphabetically. + Set set1 = new HashSet<>(); +@@ -279,16 +290,24 @@ public class PlayerAdvancements { + for (AdvancementNode advancementNode : this.rootsToUpdate) { + this.updateTreeVisibility(advancementNode, set, set1); + } ++ this.rootsToUpdate.clear(); // Roots processed, clear the set + +- this.rootsToUpdate.clear(); ++ if (!relevantProgressSet.isEmpty()) { ++ Set toProcess = useConcurrent ? new HashSet<>(relevantProgressSet) : relevantProgressSet; + +- for (AdvancementHolder advancementHolder : this.progressChanged) { +- if (this.visible.contains(advancementHolder)) { ++ for (AdvancementHolder advancementHolder : toProcess) { ++ if (this.visible.contains(advancementHolder)) { // Only include progress for visible advancements + map.put(advancementHolder.id(), this.progress.get(advancementHolder)); + } + } + +- this.progressChanged.clear(); ++ if (useConcurrent) { ++ this.progressChangedConcurrent.removeAll(toProcess); // Remove processed items from concurrent set ++ } else { ++ this.progressChanged.clear(); // Clear the regular set ++ } ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + if (!map.isEmpty() || !set.isEmpty() || !set1.isEmpty()) { + serverPlayer.connection.send(new ClientboundUpdateAdvancementsPacket(this.isFirstPacket, set, set1, map)); + } +@@ -331,10 +350,13 @@ public class PlayerAdvancements { + AdvancementHolder advancementHolder = node.holder(); + if (visible) { + if (this.visible.add(advancementHolder)) { +- advancementOutput.add(advancementHolder); ++ // Leaf start - SparklyPaper - parallel world ticking ++ advancementOutput.add(advancementHolder); // Add to visible set for packet + if (this.progress.containsKey(advancementHolder)) { +- this.progressChanged.add(advancementHolder); ++ // If progress exists, mark it changed so the progress data is sent ++ (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? this.progressChangedConcurrent : this.progressChanged).add(advancementHolder); + } ++ // Leaf end - SparklyPaper - parallel world ticking + } + } else if (this.visible.remove(advancementHolder)) { + idOutput.add(advancementHolder.id()); diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java index d4048661575ebfaf128ba25da365843774364e0e..33dd16a26edd2974f04d9a868d3e58e8e3060032 100644 --- a/net/minecraft/server/dedicated/DedicatedServer.java @@ -441,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 5da07e22ef9dac7baca9d8450b7eae3f6fa141b1..4ecb9a4125233f91379fd2792112aca6bbb3e33f 100644 +index ae5d3de44fb710b48fdabf04f5e706df1f9889b7..31abf2da10bc9b4b7825ed4b3d4e9da52feb2e39 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 @@ -469,7 +596,7 @@ index 5da07e22ef9dac7baca9d8450b7eae3f6fa141b1..4ecb9a4125233f91379fd2792112aca6 } // Paper start -@@ -1287,9 +1289,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1313,9 +1315,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe fluidState.tick(this, pos, blockState); } // Paper start - rewrite chunk system @@ -484,7 +611,7 @@ index 5da07e22ef9dac7baca9d8450b7eae3f6fa141b1..4ecb9a4125233f91379fd2792112aca6 // Paper end - rewrite chunk system } -@@ -1300,9 +1305,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1326,9 +1331,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe blockState.tick(this, pos, this.random); } // Paper start - rewrite chunk system @@ -499,7 +626,7 @@ index 5da07e22ef9dac7baca9d8450b7eae3f6fa141b1..4ecb9a4125233f91379fd2792112aca6 // Paper end - rewrite chunk system } -@@ -1553,6 +1561,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1579,6 +1587,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } private void addPlayer(ServerPlayer player) { @@ -508,7 +635,7 @@ index 5da07e22ef9dac7baca9d8450b7eae3f6fa141b1..4ecb9a4125233f91379fd2792112aca6 Entity entity = this.getEntities().get(player.getUUID()); if (entity != null) { LOGGER.warn("Force-added player with duplicate UUID {}", player.getUUID()); -@@ -1565,7 +1575,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1591,7 +1601,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // CraftBukkit start private boolean addEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { @@ -577,7 +704,7 @@ index e2b15968e89a532ec21c786f41b7f9322fd65a04..915498dcb08a678256e6cc1659642d61 // 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 f59662da0bbfe0e768c4ac5c7491d13263ac5cac..bc412a62184757547140860617b8845263905710 100644 +index b17c8a2f5294ac28cc05fb05c84a041b2c6c8721..0b8b4658dbbad1bacc13e97b4fc0cdcea7e36a06 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java @@ -252,6 +252,8 @@ public abstract class PlayerList { diff --git a/leaf-server/minecraft-patches/features/0208-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch b/leaf-archived-patches/work/server/0208-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0208-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch rename to leaf-archived-patches/work/server/0208-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch diff --git a/leaf-server/minecraft-patches/features/0140-Configurable-connection-message.patch b/leaf-server/minecraft-patches/features/0140-Configurable-connection-message.patch index 0a33847f..93f9acd3 100644 --- a/leaf-server/minecraft-patches/features/0140-Configurable-connection-message.patch +++ b/leaf-server/minecraft-patches/features/0140-Configurable-connection-message.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Configurable connection message diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java -index c18921f4ec1d4c205fa1d6efbb60eb05dcec4908..a289c3247ef6e1b7ae76fdc86c286e7b426731b4 100644 +index c18921f4ec1d4c205fa1d6efbb60eb05dcec4908..2d626735c75caa3ff6d5435882c4303aa204bd1e 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java @@ -336,7 +336,7 @@ public abstract class PlayerList { @@ -35,7 +35,7 @@ index c18921f4ec1d4c205fa1d6efbb60eb05dcec4908..a289c3247ef6e1b7ae76fdc86c286e7b this.cserver.getPluginManager().callEvent(playerQuitEvent); player.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); -@@ -1527,4 +1527,34 @@ public abstract class PlayerList { +@@ -1527,4 +1527,40 @@ public abstract class PlayerList { public boolean isAllowCommandsForAllPlayers() { return this.allowCommandsForAllPlayers; } @@ -47,7 +47,10 @@ index c18921f4ec1d4c205fa1d6efbb60eb05dcec4908..a289c3247ef6e1b7ae76fdc86c286e7b + return io.papermc.paper.adventure.PaperAdventure.asAdventure(defaultJoinMsg); + } + -+ return net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.dreeam.leaf.config.modules.misc.ConnectionMessage.joinMessage) ++ final String joinMessage = org.dreeam.leaf.config.modules.misc.ConnectionMessage.joinMessage ++ .replace("", craftPlayer.getName()); ++ ++ return net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(joinMessage) + .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("").replacement(craftPlayer.getName()).build()) + .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("").replacement(craftPlayer.displayName()).build()); + } @@ -61,7 +64,10 @@ index c18921f4ec1d4c205fa1d6efbb60eb05dcec4908..a289c3247ef6e1b7ae76fdc86c286e7b + return defaultJoinMsg; + } + -+ return net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.dreeam.leaf.config.modules.misc.ConnectionMessage.quitMessage) ++ final String quitMessage = org.dreeam.leaf.config.modules.misc.ConnectionMessage.quitMessage ++ .replace("", craftPlayer.getName()); ++ ++ return net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(quitMessage) + .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("").replacement(craftPlayer.getName()).build()) + .replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("").replacement(craftPlayer.displayName()).build()); + } diff --git a/leaf-server/minecraft-patches/features/0170-Multithreaded-Tracker.patch b/leaf-server/minecraft-patches/features/0170-Multithreaded-Tracker.patch index 170d991f..aa2512ca 100644 --- a/leaf-server/minecraft-patches/features/0170-Multithreaded-Tracker.patch +++ b/leaf-server/minecraft-patches/features/0170-Multithreaded-Tracker.patch @@ -199,7 +199,7 @@ index 209a2b6a30d334fc4f6d0b1c02682db7f0b5e435..1489ecc2754901c6f30ec1b5ff0f324b attributesToSync.clear(); diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 66177932ec65b7c4c2df0c021eeefbdb51f71eea..fc1430cb711a86281cfc7b7c94221e7ef867da9e 100644 +index 5943b18f172fb1d77ef1fe768daa8e8f43c3c8c1..7b85a9ebdbe3e8bee0a8fc100ede8a3f07eee5ce 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -2503,7 +2503,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -211,6 +211,15 @@ index 66177932ec65b7c4c2df0c021eeefbdb51f71eea..fc1430cb711a86281cfc7b7c94221e7e return this.moonrise$getEntityLookup(); // Paper - rewrite chunk system } +@@ -2739,7 +2739,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + map.carriedByPlayers.remove(player); +- if (map.carriedBy.removeIf(holdingPlayer -> holdingPlayer.player == player)) { ++ if (map.carriedBy.removeIf(holdingPlayer -> holdingPlayer != null && holdingPlayer.player == player)) { // Leaf - Multithreaded tracker + map.decorations.remove(player.getName().getString()); + } + } diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java index 27ca0be25253b35ebfe54b725f9ba28a120f4ea0..94dd2887398cbebb60200e9a5c61c01b2fda0a7f 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -264,3 +273,15 @@ index 701025715e0aca3c1f920a66f9b3d03ec08eaf02..2b8b335cf5779d1b6eb639935d1b92d8 private final AttributeSupplier supplier; private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables +diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +index 3ae69b17fec1cdb2bee2b5a795026a875f197c30..df471cd42f4084facb895b229c261b685054c3ae 100644 +--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -211,6 +211,7 @@ public class MapItemSavedData extends SavedData { + + for (int i = 0; i < this.carriedBy.size(); i++) { + MapItemSavedData.HoldingPlayer holdingPlayer1 = this.carriedBy.get(i); ++ if (holdingPlayer1 == null) continue; // Leaf - Multithreaded tracker + Player player1 = holdingPlayer1.player; + String string = player1.getName().getString(); + if (!player1.isRemoved() && (player1.getInventory().contains(predicate) || mapStack.isFramed())) { diff --git a/leaf-server/minecraft-patches/features/0171-Nitori-Async-playerdata-Save.patch b/leaf-server/minecraft-patches/features/0171-Nitori-Async-playerdata-saving.patch similarity index 68% rename from leaf-server/minecraft-patches/features/0171-Nitori-Async-playerdata-Save.patch rename to leaf-server/minecraft-patches/features/0171-Nitori-Async-playerdata-saving.patch index 5588d163..27f993f0 100644 --- a/leaf-server/minecraft-patches/features/0171-Nitori-Async-playerdata-Save.patch +++ b/leaf-server/minecraft-patches/features/0171-Nitori-Async-playerdata-saving.patch @@ -1,7 +1,7 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Fri, 23 Aug 2024 22:04:20 -0400 -Subject: [PATCH] Nitori: Async playerdata Save +Subject: [PATCH] Nitori: Async playerdata saving Original license: GPL v3 Original project: https://github.com/Gensokyo-Reimagined/Nitori @@ -24,7 +24,7 @@ index 17d3a8a2cc3c86ed6aae9c20ed9f281dc9715cf5..354823def23167feb1e7b35cd9db5ef1 public String getLocalIp() { diff --git a/net/minecraft/world/level/storage/LevelStorageSource.java b/net/minecraft/world/level/storage/LevelStorageSource.java -index 8104f71c30c1fa46c83acdf0b2e58483df9d89cc..aec5646db69d972e8c9119e62dad0cece4432ad1 100644 +index 8104f71c30c1fa46c83acdf0b2e58483df9d89cc..169b4544ab3d0e8515a2d7020a23ae0e2e0c952d 100644 --- a/net/minecraft/world/level/storage/LevelStorageSource.java +++ b/net/minecraft/world/level/storage/LevelStorageSource.java @@ -521,15 +521,26 @@ public class LevelStorageSource { @@ -45,7 +45,7 @@ index 8104f71c30c1fa46c83acdf0b2e58483df9d89cc..aec5646db69d972e8c9119e62dad0cec - LevelStorageSource.LOGGER.error("Failed to save level {}", path, var6); + LevelStorageSource.LOGGER.error("Failed to encode level {}", path, var6); } -+ org.dreeam.leaf.async.AsyncPlayerDataSaving.save(() -> { ++ org.dreeam.leaf.async.AsyncPlayerDataSaving.submit(() -> { + try { + Path path1 = Files.createTempFile(path, "level", ".dat"); + org.apache.commons.io.FileUtils.writeByteArrayToFile(path1.toFile(), nbtBytes.array, 0, nbtBytes.length, false); @@ -61,25 +61,25 @@ index 8104f71c30c1fa46c83acdf0b2e58483df9d89cc..aec5646db69d972e8c9119e62dad0cec public Optional getIconFile() { diff --git a/net/minecraft/world/level/storage/PlayerDataStorage.java b/net/minecraft/world/level/storage/PlayerDataStorage.java -index ab9282c04c1996b037567d07f95e2b150bcfcd38..fa33d4b56eec6e00912e8027195c6f63c440bc59 100644 +index ab9282c04c1996b037567d07f95e2b150bcfcd38..7a39ea109dee258e7fb83982572ee18731b5446f 100644 --- a/net/minecraft/world/level/storage/PlayerDataStorage.java +++ b/net/minecraft/world/level/storage/PlayerDataStorage.java @@ -23,6 +23,7 @@ public class PlayerDataStorage { private final File playerDir; protected final DataFixer fixerUpper; private static final DateTimeFormatter FORMATTER = FileNameDateFormatter.create(); -+ public final it.unimi.dsi.fastutil.objects.ObjectOpenHashSet savingQueue = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(); // Leaf - Async playerdata saving ++ private final java.util.Map> savingLocks = new it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap<>(); // Leaf - Async playerdata saving public PlayerDataStorage(LevelStorageSource.LevelStorageAccess levelStorageAccess, DataFixer fixerUpper) { this.fixerUpper = fixerUpper; -@@ -32,17 +33,43 @@ public class PlayerDataStorage { +@@ -32,19 +33,82 @@ public class PlayerDataStorage { public void save(Player player) { if (org.spigotmc.SpigotConfig.disablePlayerDataSaving) return; // Spigot + // Leaf start - Async playerdata saving -+ var nbtBytes = new it.unimi.dsi.fastutil.io.FastByteArrayOutputStream(65536); ++ CompoundTag compoundTag; try { - CompoundTag compoundTag = player.saveWithoutId(new CompoundTag()); +- CompoundTag compoundTag = player.saveWithoutId(new CompoundTag()); - Path path = this.playerDir.toPath(); - Path path1 = Files.createTempFile(path, player.getStringUUID() + "-", ".dat"); - NbtIo.writeCompressed(compoundTag, path1); @@ -88,43 +88,83 @@ index ab9282c04c1996b037567d07f95e2b150bcfcd38..fa33d4b56eec6e00912e8027195c6f63 - Util.safeReplaceFile(path2, path1, path3); - } catch (Exception var7) { - LOGGER.warn("Failed to save player data for {}", player.getScoreboardName(), var7); // Paper - Print exception -+ NbtIo.writeCompressed(compoundTag, nbtBytes); ++ compoundTag = player.saveWithoutId(new CompoundTag()); + } catch (Exception exception) { + LOGGER.warn("Failed to encode player data for {}", player.getScoreboardName(), exception); ++ return; } -+ String playerName = player.getScoreboardName(); -+ String stringUuid = player.getStringUUID(); -+ java.util.UUID playerUuid = player.getUUID(); -+ synchronized (PlayerDataStorage.this) { -+ while (savingQueue.contains(playerUuid)) { -+ try { -+ Thread.sleep(1L); -+ } catch (InterruptedException ignored) { -+ } -+ } -+ savingQueue.add(playerUuid); -+ } -+ org.dreeam.leaf.async.AsyncPlayerDataSaving.save(() -> { -+ try { -+ Path path = this.playerDir.toPath(); -+ Path path1 = Files.createTempFile(path, stringUuid + "-", ".dat"); -+ org.apache.commons.io.FileUtils.writeByteArrayToFile(path1.toFile(), nbtBytes.array, 0, nbtBytes.length, false); -+ Path path2 = path.resolve(stringUuid + ".dat"); -+ Path path3 = path.resolve(stringUuid + ".dat_old"); -+ Util.safeReplaceFile(path2, path1, path3); -+ } catch (Exception var7) { -+ LOGGER.warn("Failed to save player data for {}", playerName, var7); // Paper - Print exception -+ } finally { -+ synchronized (PlayerDataStorage.this) { -+ savingQueue.remove(playerUuid); -+ } -+ } -+ }); ++ save(player.getScoreboardName(), player.getUUID(), player.getStringUUID(), compoundTag); + // Leaf end - Async playerdata saving } ++ // Leaf start - Async playerdata saving ++ public void save(String playerName, java.util.UUID uniqueId, String stringId, CompoundTag compoundTag) { ++ var nbtBytes = new it.unimi.dsi.fastutil.io.FastByteArrayOutputStream(65536); ++ try { ++ NbtIo.writeCompressed(compoundTag, nbtBytes); ++ } catch (Exception exception) { ++ LOGGER.warn("Failed to encode player data for {}", stringId, exception); ++ } ++ lockFor(uniqueId, playerName); ++ synchronized (PlayerDataStorage.this) { ++ org.dreeam.leaf.async.AsyncPlayerDataSaving.submit(() -> { ++ try { ++ Path path = this.playerDir.toPath(); ++ Path path1 = Files.createTempFile(path, stringId + "-", ".dat"); ++ org.apache.commons.io.FileUtils.writeByteArrayToFile(path1.toFile(), nbtBytes.array, 0, nbtBytes.length, false); ++ Path path2 = path.resolve(stringId + ".dat"); ++ Path path3 = path.resolve(stringId + ".dat_old"); ++ Util.safeReplaceFile(path2, path1, path3); ++ } catch (Exception var7) { ++ LOGGER.warn("Failed to save player data for {}", playerName, var7); ++ } finally { ++ synchronized (PlayerDataStorage.this) { ++ savingLocks.remove(uniqueId); ++ } ++ } ++ }).ifPresent(future -> savingLocks.put(uniqueId, future)); ++ } ++ } ++ ++ private void lockFor(java.util.UUID uniqueId, String playerName) { ++ java.util.concurrent.Future fut; ++ synchronized (this) { ++ fut = savingLocks.get(uniqueId); ++ } ++ if (fut == null) { ++ return; ++ } ++ while (true) { ++ try { ++ fut.get(10_000L, java.util.concurrent.TimeUnit.MILLISECONDS); ++ break; ++ } catch (InterruptedException ignored) { ++ } catch (java.util.concurrent.ExecutionException ++ | java.util.concurrent.TimeoutException exception) { ++ LOGGER.warn("Failed to save player data for {}", playerName, exception); ++ ++ String threadDump = ""; ++ var threadMXBean = java.lang.management.ManagementFactory.getThreadMXBean(); ++ for (var threadInfo : threadMXBean.dumpAllThreads(true, true)) { ++ if (threadInfo.getThreadName().equals("Leaf IO Thread")) { ++ threadDump = threadInfo.toString(); ++ break; ++ } ++ } ++ LOGGER.warn(threadDump); ++ fut.cancel(true); ++ break; ++ } finally { ++ savingLocks.remove(uniqueId); ++ } ++ } ++ } ++ // Leaf end - Async playerdata saving ++ private void backup(String name, String stringUuid, String suffix) { // CraftBukkit -@@ -58,7 +85,20 @@ public class PlayerDataStorage { + Path path = this.playerDir.toPath(); + Path path1 = path.resolve(stringUuid + suffix); // CraftBukkit +@@ -58,7 +122,13 @@ public class PlayerDataStorage { } } @@ -134,19 +174,12 @@ index ab9282c04c1996b037567d07f95e2b150bcfcd38..fa33d4b56eec6e00912e8027195c6f63 + return load(name, stringUuid, suffix, java.util.UUID.fromString(stringUuid)); + } + private Optional load(String name, String stringUuid, String suffix, java.util.UUID playerUuid) { // CraftBukkit -+ synchronized (PlayerDataStorage.this) { -+ while (savingQueue.contains(playerUuid)) { -+ try { -+ Thread.sleep(1L); -+ } catch (InterruptedException ignored) { -+ } -+ } -+ } ++ lockFor(playerUuid, name); + // Leaf end - Async playerdata saving File file = new File(this.playerDir, stringUuid + suffix); // CraftBukkit // Spigot start boolean usingWrongFile = false; -@@ -89,7 +129,7 @@ public class PlayerDataStorage { +@@ -89,7 +159,7 @@ public class PlayerDataStorage { public Optional load(Player player) { // CraftBukkit start @@ -155,7 +188,7 @@ index ab9282c04c1996b037567d07f95e2b150bcfcd38..fa33d4b56eec6e00912e8027195c6f63 if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { org.bukkit.craftbukkit.entity.CraftPlayer craftPlayer = serverPlayer.getBukkitEntity(); // Only update first played if it is older than the one we have -@@ -104,20 +144,25 @@ public class PlayerDataStorage { +@@ -104,20 +174,25 @@ public class PlayerDataStorage { }); } diff --git a/leaf-server/minecraft-patches/features/0209-Use-ensureCapacity-to-pre-populate-the-size-of-ticki.patch b/leaf-server/minecraft-patches/features/0208-Use-ensureCapacity-to-pre-populate-the-size-of-ticki.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0209-Use-ensureCapacity-to-pre-populate-the-size-of-ticki.patch rename to leaf-server/minecraft-patches/features/0208-Use-ensureCapacity-to-pre-populate-the-size-of-ticki.patch diff --git a/leaf-server/minecraft-patches/features/0210-Directly-use-the-pre-filtered-ticking-chunks-list-as.patch b/leaf-server/minecraft-patches/features/0209-Directly-use-the-pre-filtered-ticking-chunks-list-as.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0210-Directly-use-the-pre-filtered-ticking-chunks-list-as.patch rename to leaf-server/minecraft-patches/features/0209-Directly-use-the-pre-filtered-ticking-chunks-list-as.patch diff --git a/leaf-server/minecraft-patches/features/0211-Optimize-AABB.patch b/leaf-server/minecraft-patches/features/0210-Optimize-AABB.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0211-Optimize-AABB.patch rename to leaf-server/minecraft-patches/features/0210-Optimize-AABB.patch diff --git a/leaf-server/minecraft-patches/features/0212-Improve-sorting-in-SortedArraySet.patch b/leaf-server/minecraft-patches/features/0211-Improve-sorting-in-SortedArraySet.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0212-Improve-sorting-in-SortedArraySet.patch rename to leaf-server/minecraft-patches/features/0211-Improve-sorting-in-SortedArraySet.patch diff --git a/leaf-server/minecraft-patches/features/0213-Make-removeIf-slightly-faster.patch b/leaf-server/minecraft-patches/features/0212-Make-removeIf-slightly-faster.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0213-Make-removeIf-slightly-faster.patch rename to leaf-server/minecraft-patches/features/0212-Make-removeIf-slightly-faster.patch diff --git a/leaf-server/minecraft-patches/features/0214-Optimize-LinearPalette.patch b/leaf-server/minecraft-patches/features/0213-Optimize-LinearPalette.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0214-Optimize-LinearPalette.patch rename to leaf-server/minecraft-patches/features/0213-Optimize-LinearPalette.patch diff --git a/leaf-server/minecraft-patches/features/0215-Slightly-optimized-VarInt-write.patch b/leaf-server/minecraft-patches/features/0214-Slightly-optimized-VarInt-write.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0215-Slightly-optimized-VarInt-write.patch rename to leaf-server/minecraft-patches/features/0214-Slightly-optimized-VarInt-write.patch diff --git a/leaf-server/minecraft-patches/features/0216-Rewrite-ClientboundLightUpdatePacketData.patch b/leaf-server/minecraft-patches/features/0215-Rewrite-ClientboundLightUpdatePacketData.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0216-Rewrite-ClientboundLightUpdatePacketData.patch rename to leaf-server/minecraft-patches/features/0215-Rewrite-ClientboundLightUpdatePacketData.patch diff --git a/leaf-server/minecraft-patches/features/0217-Some-Optimizations-on-SerializableChunkData.patch b/leaf-server/minecraft-patches/features/0216-Some-Optimizations-on-SerializableChunkData.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0217-Some-Optimizations-on-SerializableChunkData.patch rename to leaf-server/minecraft-patches/features/0216-Some-Optimizations-on-SerializableChunkData.patch diff --git a/leaf-server/minecraft-patches/features/0218-Rework-ChunkHolderManager.patch b/leaf-server/minecraft-patches/features/0217-Rework-ChunkHolderManager.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0218-Rework-ChunkHolderManager.patch rename to leaf-server/minecraft-patches/features/0217-Rework-ChunkHolderManager.patch diff --git a/leaf-server/minecraft-patches/features/0219-Optimize-chunkUnload.patch b/leaf-server/minecraft-patches/features/0218-Optimize-chunkUnload.patch similarity index 99% rename from leaf-server/minecraft-patches/features/0219-Optimize-chunkUnload.patch rename to leaf-server/minecraft-patches/features/0218-Optimize-chunkUnload.patch index fc37510a..c56a2883 100644 --- a/leaf-server/minecraft-patches/features/0219-Optimize-chunkUnload.patch +++ b/leaf-server/minecraft-patches/features/0218-Optimize-chunkUnload.patch @@ -159,7 +159,7 @@ index 4ca68a903e67606fc4ef0bfa9862a73797121c8b..bed3a64388bb43e47c2ba4e67f7dde5b public static final class SaveState { diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java -index 963c51d14f87d2557a3d686fb8fe3ec9cba367b3..492618b13ecc7ba541339fea2f4ea4daddfa143f 100644 +index 36c033b0ee63dfc273d721fb4b614733e8fdef19..dc9f1bc6dd8b1057da3416e24f15f2329658f996 100644 --- a/net/minecraft/world/level/chunk/LevelChunkSection.java +++ b/net/minecraft/world/level/chunk/LevelChunkSection.java @@ -24,6 +24,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ diff --git a/leaf-server/minecraft-patches/features/0220-Async-chunk-sending.patch b/leaf-server/minecraft-patches/features/0219-Async-chunk-sending.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0220-Async-chunk-sending.patch rename to leaf-server/minecraft-patches/features/0219-Async-chunk-sending.patch diff --git a/leaf-server/minecraft-patches/features/0221-Spawner-Configurations.patch b/leaf-server/minecraft-patches/features/0220-Spawner-Configurations.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0221-Spawner-Configurations.patch rename to leaf-server/minecraft-patches/features/0220-Spawner-Configurations.patch diff --git a/leaf-server/minecraft-patches/features/0222-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch b/leaf-server/minecraft-patches/features/0221-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0222-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch rename to leaf-server/minecraft-patches/features/0221-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch diff --git a/leaf-server/minecraft-patches/features/0223-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch b/leaf-server/minecraft-patches/features/0222-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0223-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch rename to leaf-server/minecraft-patches/features/0222-Optimize-SetLookAndInteract-and-NearestVisibleLiving.patch diff --git a/leaf-server/minecraft-patches/features/0224-Remove-streams-on-InsideBrownianWalk.patch b/leaf-server/minecraft-patches/features/0223-Remove-streams-on-InsideBrownianWalk.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0224-Remove-streams-on-InsideBrownianWalk.patch rename to leaf-server/minecraft-patches/features/0223-Remove-streams-on-InsideBrownianWalk.patch diff --git a/leaf-server/minecraft-patches/features/0225-Use-BFS-on-getSlopeDistance.patch b/leaf-server/minecraft-patches/features/0224-Use-BFS-on-getSlopeDistance.patch similarity index 85% rename from leaf-server/minecraft-patches/features/0225-Use-BFS-on-getSlopeDistance.patch rename to leaf-server/minecraft-patches/features/0224-Use-BFS-on-getSlopeDistance.patch index 43e58292..43717629 100644 --- a/leaf-server/minecraft-patches/features/0225-Use-BFS-on-getSlopeDistance.patch +++ b/leaf-server/minecraft-patches/features/0224-Use-BFS-on-getSlopeDistance.patch @@ -9,27 +9,37 @@ Leaf: ~48ms (-36%) This should help drastically on the farms that use actively changing fluids. diff --git a/net/minecraft/world/level/material/FlowingFluid.java b/net/minecraft/world/level/material/FlowingFluid.java -index 4625bd55e1cb01dfb9921dcd033f05b4a8f9ad74..8646ccf77f738d7b66efd0a7ecf2519d8a8d6bdf 100644 +index 4625bd55e1cb01dfb9921dcd033f05b4a8f9ad74..c0f78556d112e59333ace60f1522e7fd1efe71c3 100644 --- a/net/minecraft/world/level/material/FlowingFluid.java +++ b/net/minecraft/world/level/material/FlowingFluid.java -@@ -8,6 +8,8 @@ import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; - import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; - import java.util.Map; - import java.util.Map.Entry; -+import java.util.Queue; -+ - import net.minecraft.core.BlockPos; - import net.minecraft.core.Direction; - import net.minecraft.server.level.ServerLevel; -@@ -342,31 +344,72 @@ public abstract class FlowingFluid extends Fluid { +@@ -342,32 +342,81 @@ public abstract class FlowingFluid extends Fluid { protected abstract void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state); - protected int getSlopeDistance(LevelReader level, BlockPos pos, int depth, Direction direction, BlockState state, FlowingFluid.SpreadContext spreadContext) { - int i = 1000; ++ // Leaf start - Use BFS on getSlopeDistance + protected int getSlopeDistance(LevelReader level, BlockPos startPos, int initialDepth, Direction excludedDirection, BlockState startState, FlowingFluid.SpreadContext spreadContext) { -+ it.unimi.dsi.fastutil.longs.LongSet visited = new it.unimi.dsi.fastutil.longs.LongOpenHashSet(512); // Pre-allocate capacity -+ java.util.Queue queue = new java.util.ArrayDeque<>(64); // Optimized initial capacity ++ it.unimi.dsi.fastutil.longs.LongSet visited = new it.unimi.dsi.fastutil.longs.LongOpenHashSet(512); ++ java.util.Queue queue = new java.util.ArrayDeque<>(256); ++ ++ for (Direction dir : Direction.Plane.HORIZONTAL) { ++ if (dir == excludedDirection) continue; ++ ++ BlockPos neighborPos = startPos.relative(dir); ++ BlockState neighborState = spreadContext.getBlockStateIfLoaded(neighborPos); ++ if (neighborState == null) continue; ++ ++ // Check if the fluid can actually pass through to this first neighbor before adding ++ FluidState neighborFluidState = neighborState.getFluidState(); ++ if (!this.canPassThrough(level, this.getFlowing(), startPos, startState, dir, neighborPos, neighborState, neighborFluidState)) { ++ continue; ++ } ++ long visitKey = encodeSlopeNode(neighborPos, dir.getOpposite()); ++ if (visited.add(visitKey)) { ++ queue.add(new FlowingFluid.SlopeDistanceNode(neighborPos, initialDepth, dir.getOpposite(), neighborState)); ++ } ++ } - for (Direction direction1 : Direction.Plane.HORIZONTAL) { - if (direction1 != direction) { @@ -41,8 +51,8 @@ index 4625bd55e1cb01dfb9921dcd033f05b4a8f9ad74..8646ccf77f738d7b66efd0a7ecf2519d - if (spreadContext.isHole(blockPos)) { - return depth; - } -+ for (Direction dir : Direction.Plane.HORIZONTAL) { -+ if (dir == excludedDirection) continue; ++ int slopeFindDistance = this.getSlopeFindDistance(level); ++ int minDistance = 1000; - if (depth < this.getSlopeFindDistance(level)) { - int slopeDistance = this.getSlopeDistance(level, blockPos, depth + 1, direction1.getOpposite(), blockState, spreadContext); @@ -50,27 +60,15 @@ index 4625bd55e1cb01dfb9921dcd033f05b4a8f9ad74..8646ccf77f738d7b66efd0a7ecf2519d - i = slopeDistance; - } - } -+ BlockPos neighborPos = startPos.relative(dir); -+ BlockState neighborState = spreadContext.getBlockStateIfLoaded(neighborPos); -+ if (neighborState == null) continue; -+ long visitKey = encodeSlopeNode(neighborPos, dir.getOpposite()); -+ if (visited.add(visitKey)) { -+ queue.add(new FlowingFluid.SlopeDistanceNode(neighborPos, initialDepth + 1, dir.getOpposite(), neighborState)); -+ } -+ } -+ -+ int slopeFindDistance = this.getSlopeFindDistance(level); -+ int minDistance = 1000; -+ + // Process the queue + while (!queue.isEmpty()) { + FlowingFluid.SlopeDistanceNode current = queue.poll(); -+ if (current.depth >= slopeFindDistance) continue; -+ + if (spreadContext.isHole(current.pos)) { + return current.depth; + } + ++ if (current.depth >= slopeFindDistance) continue; ++ + for (Direction dir : Direction.Plane.HORIZONTAL) { + if (dir == current.excludedDir) continue; + @@ -91,7 +89,7 @@ index 4625bd55e1cb01dfb9921dcd033f05b4a8f9ad74..8646ccf77f738d7b66efd0a7ecf2519d } - return i; -+ return minDistance; // Return fallback value ++ return minDistance; + } + + private static long encodeSlopeNode(BlockPos pos, Direction excludedDir) { @@ -111,5 +109,7 @@ index 4625bd55e1cb01dfb9921dcd033f05b4a8f9ad74..8646ccf77f738d7b66efd0a7ecf2519d + this.state = state; + } } ++ // Leaf end - Use BFS on getSlopeDistance boolean isWaterHole(BlockGetter level, BlockPos pos, BlockState state, BlockPos belowPos, BlockState belowState) { + return canPassThroughWall(Direction.DOWN, level, pos, state, belowPos, belowState) diff --git a/leaf-server/minecraft-patches/features/0226-Paper-PR-Throttle-failed-spawn-attempts.patch b/leaf-server/minecraft-patches/features/0225-Paper-PR-Throttle-failed-spawn-attempts.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0226-Paper-PR-Throttle-failed-spawn-attempts.patch rename to leaf-server/minecraft-patches/features/0225-Paper-PR-Throttle-failed-spawn-attempts.patch diff --git a/leaf-server/minecraft-patches/features/0227-Improve-BlockEntity-ticking-isRemoved-check.patch b/leaf-server/minecraft-patches/features/0226-Improve-BlockEntity-ticking-isRemoved-check.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0227-Improve-BlockEntity-ticking-isRemoved-check.patch rename to leaf-server/minecraft-patches/features/0226-Improve-BlockEntity-ticking-isRemoved-check.patch diff --git a/leaf-server/minecraft-patches/features/0228-Raytrace-AntiXray-SDK-integration.patch b/leaf-server/minecraft-patches/features/0227-Raytrace-AntiXray-SDK-integration.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0228-Raytrace-AntiXray-SDK-integration.patch rename to leaf-server/minecraft-patches/features/0227-Raytrace-AntiXray-SDK-integration.patch diff --git a/leaf-server/minecraft-patches/features/0229-Optimize-addOrUpdateTransientModifier.patch b/leaf-server/minecraft-patches/features/0228-Optimize-addOrUpdateTransientModifier.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0229-Optimize-addOrUpdateTransientModifier.patch rename to leaf-server/minecraft-patches/features/0228-Optimize-addOrUpdateTransientModifier.patch diff --git a/leaf-server/minecraft-patches/features/0230-Optimize-ContextMap.create.patch b/leaf-server/minecraft-patches/features/0229-Optimize-ContextMap.create.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0230-Optimize-ContextMap.create.patch rename to leaf-server/minecraft-patches/features/0229-Optimize-ContextMap.create.patch diff --git a/leaf-server/minecraft-patches/features/0231-Micro-optimizations-for-random-tick.patch b/leaf-server/minecraft-patches/features/0230-Micro-optimizations-for-random-tick.patch similarity index 97% rename from leaf-server/minecraft-patches/features/0231-Micro-optimizations-for-random-tick.patch rename to leaf-server/minecraft-patches/features/0230-Micro-optimizations-for-random-tick.patch index 47581da3..8d0fe708 100644 --- a/leaf-server/minecraft-patches/features/0231-Micro-optimizations-for-random-tick.patch +++ b/leaf-server/minecraft-patches/features/0230-Micro-optimizations-for-random-tick.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Micro optimizations for random tick diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index d8390cb3901a40b97e99990d9f71f12c74f96607..4d7cf866452db7388ab90f9be284e859b39d8e61 100644 +index 7ca4fd418599cdb1bb1de44f4c3c57f1770a4038..461b620b703c9fca2691f724a9b865ad54aa90a4 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -903,7 +903,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe diff --git a/leaf-server/minecraft-patches/features/0232-Remove-streams-on-updateConnectedPlayersWithinRange.patch b/leaf-server/minecraft-patches/features/0231-Remove-streams-on-updateConnectedPlayersWithinRange.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0232-Remove-streams-on-updateConnectedPlayersWithinRange.patch rename to leaf-server/minecraft-patches/features/0231-Remove-streams-on-updateConnectedPlayersWithinRange.patch diff --git a/leaf-server/minecraft-patches/features/0233-Remove-streams-on-PlayerDetector.patch b/leaf-server/minecraft-patches/features/0232-Remove-streams-on-PlayerDetector.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0233-Remove-streams-on-PlayerDetector.patch rename to leaf-server/minecraft-patches/features/0232-Remove-streams-on-PlayerDetector.patch diff --git a/leaf-server/minecraft-patches/features/0233-Async-Block-Finding.patch b/leaf-server/minecraft-patches/features/0233-Async-Block-Finding.patch new file mode 100644 index 00000000..ca8e3fac --- /dev/null +++ b/leaf-server/minecraft-patches/features/0233-Async-Block-Finding.patch @@ -0,0 +1,183 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sun, 23 Mar 2025 11:51:44 +0100 +Subject: [PATCH] Async Block Finding + + +diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..007da9cb39ff76285c52ce0abdff60997acdff0f 100644 +--- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java ++++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +@@ -20,6 +20,18 @@ public abstract class MoveToBlockGoal extends Goal { + private final int verticalSearchRange; + protected int verticalSearchStart; + ++ // Leaf start - Async Block Finding ++ private static final java.util.concurrent.ExecutorService BLOCK_FINDER_EXECUTOR = ++ java.util.concurrent.Executors.newSingleThreadExecutor( ++ new com.google.common.util.concurrent.ThreadFactoryBuilder() ++ .setNameFormat("Leaf Block Finding Thread - %d") ++ .setDaemon(true) ++ .build()); ++ ++ private final java.util.concurrent.ConcurrentLinkedQueue candidateBlocks = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ private boolean asyncSearchInProgress = false; ++ // Leaf end - Async Block Finding ++ + public MoveToBlockGoal(PathfinderMob mob, double speedModifier, int searchRange) { + this(mob, speedModifier, searchRange, 1); + } +@@ -29,6 +41,10 @@ public abstract class MoveToBlockGoal extends Goal { + super.stop(); + this.blockPos = BlockPos.ZERO; + this.mob.movingTarget = null; ++ // Leaf start - Async Block Finding - Reset async state on goal stop ++ this.candidateBlocks.clear(); ++ this.asyncSearchInProgress = false; ++ // Leaf end - Async Block Finding - Reset async state on goal stop + } + // Paper end + +@@ -53,23 +69,23 @@ public abstract class MoveToBlockGoal extends Goal { + } + + protected int nextStartTick(PathfinderMob creature) { +- return reducedTickDelay(200 + creature.getRandom().nextInt(200)); ++ return Goal.reducedTickDelay(200 + creature.getRandom().nextInt(200)); // Leaf - Async Block Finding - Use the static method from the Goal class directly + } + + @Override + public boolean canContinueToUse() { +- return this.tryTicks >= -this.maxStayTicks && this.tryTicks <= 1200 && this.isValidTarget(this.mob.level(), this.blockPos); ++ return this.tryTicks >= -this.maxStayTicks && this.tryTicks <= 1200 && this.blockPos != BlockPos.ZERO && this.isValidTarget(this.mob.level(), this.blockPos); // Leaf - Async Block Finding + } + + @Override + public void start() { +- this.moveMobToBlock(); ++ if (this.blockPos != BlockPos.ZERO) this.moveMobToBlock(); // Leaf - Async Block Finding + this.tryTicks = 0; + this.maxStayTicks = this.mob.getRandom().nextInt(this.mob.getRandom().nextInt(1200) + 1200) + 1200; + } + + protected void moveMobToBlock() { +- this.mob.getNavigation().moveTo(this.blockPos.getX() + 0.5, this.blockPos.getY() + 1, this.blockPos.getZ() + 0.5, this.speedModifier); ++ if (this.blockPos != BlockPos.ZERO) this.mob.getNavigation().moveTo(this.blockPos.getX() + 0.5, this.blockPos.getY() + 1, this.blockPos.getZ() + 0.5, this.speedModifier); // Leaf - Async Block Finding + } + + public double acceptedDistance() { +@@ -77,7 +93,7 @@ public abstract class MoveToBlockGoal extends Goal { + } + + protected BlockPos getMoveToTarget() { +- return this.blockPos.above(); ++ return this.blockPos != BlockPos.ZERO ? this.blockPos.above() : BlockPos.ZERO; // Leaf - Async Block Finding + } + + @Override +@@ -87,7 +103,10 @@ public abstract class MoveToBlockGoal extends Goal { + + @Override + public void tick() { ++ if (this.blockPos == BlockPos.ZERO) return; // Leaf - Async Block Finding + BlockPos moveToTarget = this.getMoveToTarget(); ++ if (moveToTarget == BlockPos.ZERO) return; // Leaf - Async Block Finding ++ + if (!moveToTarget.closerToCenterThan(this.mob.position(), this.acceptedDistance())) { + this.reachedTarget = false; + this.tryTicks++; +@@ -109,20 +128,90 @@ public abstract class MoveToBlockGoal extends Goal { + } + + protected boolean findNearestBlock() { ++ // Leaf start - Async Block Finding ++ if (!org.dreeam.leaf.config.modules.async.AsyncBlockFinding.enabled) { ++ return findNearestBlockSync(); ++ } ++ ++ while (!candidateBlocks.isEmpty()) { ++ BlockPos pos = candidateBlocks.poll(); ++ if (pos != null && this.mob.level().hasChunkAt(pos) && ++ this.mob.isWithinRestriction(pos) && ++ this.isValidTarget(this.mob.level(), pos)) { ++ ++ this.blockPos = pos; ++ this.mob.movingTarget = pos == BlockPos.ZERO ? null : pos; ++ return true; ++ } ++ } ++ ++ if (asyncSearchInProgress) { ++ return false; ++ } ++ ++ // Check again before starting, avoids tiny race condition if canUse is called rapidly ++ if (!asyncSearchInProgress) { ++ asyncSearchInProgress = true; ++ final BlockPos centerPos = this.mob.blockPosition().immutable(); ++ final int searchRange = this.searchRange; ++ final int verticalRange = this.verticalSearchRange; ++ final int verticalStart = this.verticalSearchStart; ++ ++ BLOCK_FINDER_EXECUTOR.execute(() -> { ++ try { ++ generateCandidateBlocks(centerPos, searchRange, verticalRange, verticalStart); ++ } catch (Exception e) { ++ e.printStackTrace(); // Keep basic error logging ++ } finally { ++ asyncSearchInProgress = false; ++ } ++ }); ++ } ++ ++ return false; ++ } ++ ++ private void generateCandidateBlocks(BlockPos center, int searchRange, int verticalRange, int verticalStart) { ++ java.util.List positions = new java.util.ArrayList<>(); ++ ++ for (int i2 = verticalStart; i2 <= verticalRange; i2 = i2 > 0 ? -i2 : 1 - i2) { ++ for (int i3 = 0; i3 < searchRange; i3++) { ++ for (int i4 = 0; i4 <= i3; i4 = i4 > 0 ? -i4 : 1 - i4) { ++ for (int i5 = i4 < i3 && i4 > -i3 ? i3 : 0; i5 <= i3; i5 = i5 > 0 ? -i5 : 1 - i5) { ++ BlockPos pos = center.offset(i4, i2 - 1, i5); ++ positions.add(pos.immutable()); ++ } ++ } ++ } ++ } ++ ++ positions.sort((p1, p2) -> { ++ double d1 = p1.distSqr(center); ++ double d2 = p2.distSqr(center); ++ return Double.compare(d1, d2); ++ }); ++ ++ for (BlockPos pos : positions) { ++ candidateBlocks.add(pos); ++ } ++ } ++ ++ protected boolean findNearestBlockSync() { ++ // Leaf end - Async Block Finding + int i = this.searchRange; + int i1 = this.verticalSearchRange; +- BlockPos blockPos = this.mob.blockPosition(); ++ BlockPos blockPosOrigin = this.mob.blockPosition(); // Leaf - Async Block Finding + BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); + + for (int i2 = this.verticalSearchStart; i2 <= i1; i2 = i2 > 0 ? -i2 : 1 - i2) { + for (int i3 = 0; i3 < i; i3++) { + for (int i4 = 0; i4 <= i3; i4 = i4 > 0 ? -i4 : 1 - i4) { + for (int i5 = i4 < i3 && i4 > -i3 ? i3 : 0; i5 <= i3; i5 = i5 > 0 ? -i5 : 1 - i5) { +- mutableBlockPos.setWithOffset(blockPos, i4, i2 - 1, i5); ++ mutableBlockPos.setWithOffset(blockPosOrigin, i4, i2 - 1, i5); // Leaf - Async Block Finding + if (!this.mob.level().hasChunkAt(mutableBlockPos)) continue; // Gale - Airplane - block goal does not load chunks - if this block isn't loaded, continue + if (this.mob.isWithinRestriction(mutableBlockPos) && this.isValidTarget(this.mob.level(), mutableBlockPos)) { +- this.blockPos = mutableBlockPos; +- this.mob.movingTarget = mutableBlockPos == BlockPos.ZERO ? null : mutableBlockPos.immutable(); // Paper ++ this.blockPos = mutableBlockPos.immutable(); // Leaf - Async Block Finding ++ this.mob.movingTarget = this.blockPos == BlockPos.ZERO ? null : this.blockPos; // Paper // Leaf - Async Block Finding + return true; + } + } diff --git a/leaf-server/minecraft-patches/features/0234-Async-Block-Finding.patch b/leaf-server/minecraft-patches/features/0234-Async-Block-Finding.patch deleted file mode 100644 index 1c9e14d9..00000000 --- a/leaf-server/minecraft-patches/features/0234-Async-Block-Finding.patch +++ /dev/null @@ -1,126 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Sun, 23 Mar 2025 11:51:44 +0100 -Subject: [PATCH] Async Block Finding - - -diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..2bc0f19b86067491f33f647d2e387acd83492844 100644 ---- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -+++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -@@ -20,6 +20,19 @@ public abstract class MoveToBlockGoal extends Goal { - private final int verticalSearchRange; - protected int verticalSearchStart; - -+ // Leaf start - Async Block Finding -+ private static final java.util.concurrent.ExecutorService BLOCK_FINDER_EXECUTOR = -+ java.util.concurrent.Executors.newSingleThreadExecutor( -+ new com.google.common.util.concurrent.ThreadFactoryBuilder() -+ .setNameFormat("Leaf Block Finding Thread - %d") -+ .setDaemon(true) -+ .build()); -+ -+ private final java.util.concurrent.ConcurrentLinkedQueue candidateBlocks = new java.util.concurrent.ConcurrentLinkedQueue<>(); -+ private boolean asyncSearchInProgress = false; -+ private boolean searchComplete = false; -+ // Leaf end - Async Block Finding -+ - public MoveToBlockGoal(PathfinderMob mob, double speedModifier, int searchRange) { - this(mob, speedModifier, searchRange, 1); - } -@@ -109,6 +122,95 @@ public abstract class MoveToBlockGoal extends Goal { - } - - protected boolean findNearestBlock() { -+ // Leaf start - Async Block Finding -+ // Check if async is enabled, if not use the original implementation -+ if (!org.dreeam.leaf.config.modules.async.AsyncBlockFinding.enabled) { -+ return findNearestBlockSync(); -+ } -+ -+ // Check if we're done with the current search -+ if (searchComplete) { -+ searchComplete = false; -+ asyncSearchInProgress = false; -+ candidateBlocks.clear(); -+ return false; -+ } -+ -+ while (!candidateBlocks.isEmpty()) { -+ BlockPos pos = candidateBlocks.poll(); -+ -+ if (pos != null && this.mob.level().hasChunkAt(pos) && -+ this.mob.isWithinRestriction(pos) && -+ this.isValidTarget(this.mob.level(), pos)) { -+ -+ this.blockPos = pos; -+ this.mob.movingTarget = pos == BlockPos.ZERO ? null : pos; -+ searchComplete = true; -+ return true; -+ } -+ } -+ -+ // If no candidates are left and async search is done -+ if (candidateBlocks.isEmpty() && !asyncSearchInProgress) { -+ searchComplete = true; -+ return false; -+ } -+ -+ // Start async search if needed -+ if (!asyncSearchInProgress && candidateBlocks.isEmpty()) { -+ asyncSearchInProgress = true; -+ -+ // Get necessary data from main thread -+ final BlockPos centerPos = this.mob.blockPosition().immutable(); -+ final int searchRange = this.searchRange; -+ final int verticalRange = this.verticalSearchRange; -+ final int verticalStart = this.verticalSearchStart; -+ BLOCK_FINDER_EXECUTOR.execute(() -> { -+ try { -+ generateCandidateBlocks(centerPos, searchRange, verticalRange, verticalStart); -+ } catch (Exception e) { -+ e.printStackTrace(); -+ } finally { -+ asyncSearchInProgress = false; -+ } -+ }); -+ } -+ -+ return false; -+ } -+ -+ // Generate candidate blocks in a spiral pattern -+ private void generateCandidateBlocks(BlockPos center, int searchRange, int verticalRange, int verticalStart) { -+ // Pre-calculate a prioritized list of positions -+ java.util.List positions = new java.util.ArrayList<>(); -+ -+ for (int i2 = verticalStart; i2 <= verticalRange; i2 = i2 > 0 ? -i2 : 1 - i2) { -+ for (int i3 = 0; i3 < searchRange; i3++) { -+ for (int i4 = 0; i4 <= i3; i4 = i4 > 0 ? -i4 : 1 - i4) { -+ for (int i5 = i4 < i3 && i4 > -i3 ? i3 : 0; i5 <= i3; i5 = i5 > 0 ? -i5 : 1 - i5) { -+ BlockPos pos = center.offset(i4, i2 - 1, i5); -+ positions.add(pos.immutable()); -+ } -+ } -+ } -+ } -+ -+ // Sort by distance to center (closest first) -+ positions.sort((p1, p2) -> { -+ double d1 = p1.distSqr(center); -+ double d2 = p2.distSqr(center); -+ return Double.compare(d1, d2); -+ }); -+ -+ // Add to candidate queue -+ for (BlockPos pos : positions) { -+ candidateBlocks.add(pos); -+ } -+ } -+ -+ // The original method renamed -+ protected boolean findNearestBlockSync() { -+ // Leaf end - Async Block Finding - int i = this.searchRange; - int i1 = this.verticalSearchRange; - BlockPos blockPos = this.mob.blockPosition(); diff --git a/leaf-server/minecraft-patches/features/0235-Use-direct-iteration-on-Sensing.tick.patch b/leaf-server/minecraft-patches/features/0234-Use-direct-iteration-on-Sensing.tick.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0235-Use-direct-iteration-on-Sensing.tick.patch rename to leaf-server/minecraft-patches/features/0234-Use-direct-iteration-on-Sensing.tick.patch diff --git a/leaf-server/minecraft-patches/features/0236-Optimise-non-flush-packet-sending.patch b/leaf-server/minecraft-patches/features/0235-Optimise-non-flush-packet-sending.patch similarity index 94% rename from leaf-server/minecraft-patches/features/0236-Optimise-non-flush-packet-sending.patch rename to leaf-server/minecraft-patches/features/0235-Optimise-non-flush-packet-sending.patch index f15568e7..137d91b4 100644 --- a/leaf-server/minecraft-patches/features/0236-Optimise-non-flush-packet-sending.patch +++ b/leaf-server/minecraft-patches/features/0235-Optimise-non-flush-packet-sending.patch @@ -26,7 +26,7 @@ Locally this patch drops the entity tracker tick by a full 1.5x. Co-authored-by: Quang Tran <3d7777456@gmail.com> diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java -index 5b46036868b6c9d082e35591e58735e16adaae62..9563165c945757996da11f55e2221e620dd93327 100644 +index 00a82873d226f113278632a53c0faca420dd67d4..f3e9de8716f5e1a72ec465ee897c8f0413f7b1c3 100644 --- a/net/minecraft/network/Connection.java +++ b/net/minecraft/network/Connection.java @@ -147,6 +147,7 @@ public class Connection extends SimpleChannelInboundHandler> { @@ -45,7 +45,7 @@ index 5b46036868b6c9d082e35591e58735e16adaae62..9563165c945757996da11f55e2221e62 this.address = this.channel.remoteAddress(); this.preparing = false; // Spigot if (this.delayedDisconnect != null) { -@@ -477,6 +479,11 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -476,6 +478,11 @@ public class Connection extends SimpleChannelInboundHandler> { if (this.channel.eventLoop().inEventLoop()) { this.doSendPacket(packet, sendListener, flush); } else { diff --git a/leaf-server/minecraft-patches/features/0237-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch b/leaf-server/minecraft-patches/features/0236-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0237-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch rename to leaf-server/minecraft-patches/features/0236-Prevent-double-chunk-retrieving-in-entity-fluid-push.patch diff --git a/leaf-server/minecraft-patches/features/0237-Async-Target-Finding.patch b/leaf-server/minecraft-patches/features/0237-Async-Target-Finding.patch new file mode 100644 index 00000000..0eab7d26 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0237-Async-Target-Finding.patch @@ -0,0 +1,319 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sat, 29 Mar 2025 13:40:46 +0100 +Subject: [PATCH] Async Target Finding + + +diff --git a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java +index 024792900c8ab716e91ef512d2da22548075044d..7f256793232cfa9666728223cb9964e49ff8b6ba 100644 +--- a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java ++++ b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java +@@ -16,9 +16,37 @@ public class NearestAttackableTargetGoal extends TargetG + protected final Class targetType; + protected final int randomInterval; + @Nullable +- protected LivingEntity target; ++ protected volatile LivingEntity target; // Leaf - Async Target Finding + protected TargetingConditions targetConditions; + ++ // Leaf start - Async Target Finding ++ // Single thread executor to prevent overwhelming the server ++ private static final java.util.concurrent.ExecutorService TARGET_FINDER_EXECUTOR = java.util.concurrent.Executors.newSingleThreadExecutor(r -> { ++ Thread thread = new Thread(r, "Leaf - Target-Finder-Thread"); ++ thread.setDaemon(true); ++ thread.setPriority(Thread.MIN_PRIORITY); // Lower priority to avoid competing with main thread ++ return thread; ++ }); ++ ++ // Flag to track if a search is in progress ++ private final java.util.concurrent.atomic.AtomicBoolean isSearching = new java.util.concurrent.atomic.AtomicBoolean(false); ++ private final java.util.concurrent.atomic.AtomicReference pendingTarget = new java.util.concurrent.atomic.AtomicReference<>(null); ++ static { ++ Runtime.getRuntime().addShutdownHook(new Thread(() -> { ++ try { ++ TARGET_FINDER_EXECUTOR.shutdown(); ++ TARGET_FINDER_EXECUTOR.awaitTermination(2, java.util.concurrent.TimeUnit.SECONDS); ++ } catch (InterruptedException e) { ++ Thread.currentThread().interrupt(); ++ } finally { ++ if (!TARGET_FINDER_EXECUTOR.isTerminated()) { ++ TARGET_FINDER_EXECUTOR.shutdownNow(); ++ } ++ } ++ })); ++ } ++ // Leaf end - Async Target Finding ++ + public NearestAttackableTargetGoal(Mob mob, Class targetType, boolean mustSee) { + this(mob, targetType, 10, mustSee, false, null); + } +@@ -46,8 +74,14 @@ public class NearestAttackableTargetGoal extends TargetG + if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) { + return false; + } else { +- this.findTarget(); +- return this.target != null; ++ // Leaf start - Async Target Finding ++ findTarget(); ++ LivingEntity pending = pendingTarget.getAndSet(null); ++ if (pending != null && !pending.isRemoved() && pending.isAlive()) { ++ this.target = pending; ++ } ++ return this.target != null && this.target.isAlive() && !this.target.isRemoved(); ++ // Leaf end - Async Target Finding + } + } + +@@ -55,25 +89,239 @@ public class NearestAttackableTargetGoal extends TargetG + return this.mob.getBoundingBox().inflate(targetDistance, targetDistance, targetDistance); + } + ++ // Leaf start - Async Target Finding ++ // Async find target implementation with safer entity handling + protected void findTarget() { +- ServerLevel serverLevel = getServerLevel(this.mob); +- if (this.targetType != Player.class && this.targetType != ServerPlayer.class) { +- this.target = serverLevel.getNearestEntity( +- this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true), +- this.getTargetConditions(), +- this.mob, +- this.mob.getX(), +- this.mob.getEyeY(), +- this.mob.getZ() +- ); +- } else { +- this.target = serverLevel.getNearestPlayer(this.getTargetConditions(), this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); ++ // If async is disabled or we're already searching, use sync method ++ if (!org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled || !isSearching.compareAndSet(false, true)) { ++ findTargetSync(); ++ return; + } ++ ++ // Capture mutable state to avoid race conditions ++ final Mob mob = this.mob; ++ ++ // Safety check ++ if (mob == null || mob.isRemoved() || !mob.isAlive()) { ++ isSearching.set(false); ++ return; ++ } ++ ++ final double x = mob.getX(); ++ final double y = mob.getEyeY(); ++ final double z = mob.getZ(); ++ final double followDistance = this.getFollowDistance(); ++ final TargetingConditions targetConditions = this.getTargetConditions(); ++ final Class targetType = this.targetType; ++ ++ // Start async search with immutable captured state - using submit instead of runAsync ++ java.util.concurrent.CompletableFuture.supplyAsync(() -> { ++ try { ++ ServerLevel serverLevel = getServerLevel(mob); ++ if (serverLevel == null) { ++ return null; ++ } ++ if (mob.isRemoved() || !mob.isAlive()) { ++ return null; ++ } ++ ++ try { ++ if (targetType != Player.class && targetType != ServerPlayer.class) { ++ AABB searchArea = new AABB( ++ x - followDistance, y - followDistance, z - followDistance, ++ x + followDistance, y + followDistance, z + followDistance ++ ); ++ ++ java.util.List entities = null; ++ try { ++ entities = mob.level().getEntitiesOfClass(targetType, searchArea, entity -> true); ++ } catch (Exception e) { ++ System.err.println("Error getting entities: " + e.getMessage()); ++ return null; ++ } ++ ++ if (entities != null && !entities.isEmpty()) { ++ return findNearestEntitySafely(entities, targetConditions, mob, x, y, z, serverLevel); ++ } ++ } else { ++ return findNearestPlayerSafely(targetConditions, mob, x, y, z, serverLevel); ++ } ++ } catch (Exception e) { ++ System.err.println("Error finding entities in async target finder: " + e.getMessage()); ++ } ++ ++ return null; ++ } catch (Exception e) { ++ System.err.println("Error during async target finding: " + e.getMessage()); ++ return null; ++ } finally { ++ isSearching.set(false); ++ } ++ }, TARGET_FINDER_EXECUTOR).thenAccept(result -> { ++ if (result != null && result.isAlive() && !result.isRemoved()) { ++ pendingTarget.set(result); ++ } ++ }); + } + ++ @Nullable ++ private LivingEntity findNearestEntitySafely( ++ java.util.List entities, ++ TargetingConditions conditions, ++ Mob source, ++ double x, ++ double y, ++ double z, ++ ServerLevel level) { ++ ++ if (entities == null || entities.isEmpty() || level == null) { ++ return null; ++ } ++ ++ try { ++ double closestDistSq = -1.0; ++ LivingEntity closest = null; ++ ++ for (int i = 0; i < entities.size(); i++) { ++ try { ++ LivingEntity entity = entities.get(i); ++ if (entity == null || entity.isRemoved() || !entity.isAlive()) { ++ continue; ++ } ++ ++ if (conditions.test(level, source, entity)) { ++ double dx = entity.getX() - x; ++ double dy = entity.getY() - y; ++ double dz = entity.getZ() - z; ++ double distSq = dx * dx + dy * dy + dz * dz; ++ ++ if (closestDistSq == -1.0 || distSq < closestDistSq) { ++ closestDistSq = distSq; ++ closest = entity; ++ } ++ } ++ } catch (IndexOutOfBoundsException e) { ++ break; ++ } catch (Exception e) { ++ System.err.println("Error processing entity in findNearestEntitySafely: " + e.getMessage()); ++ continue; ++ } ++ } ++ ++ return closest; ++ } catch (Exception e) { ++ System.err.println("Error in findNearestEntitySafely: " + e.getMessage()); ++ return null; ++ } ++ } ++ ++ @Nullable ++ private Player findNearestPlayerSafely( ++ TargetingConditions conditions, ++ Mob source, ++ double x, ++ double y, ++ double z, ++ ServerLevel level) { ++ ++ if (level == null) { ++ return null; ++ } ++ ++ try { ++ java.util.List players = level.players(); ++ if (players == null || players.isEmpty()) { ++ return null; ++ } ++ ++ double closestDistSq = -1.0; ++ Player closest = null; ++ ++ for (int i = 0; i < players.size(); i++) { ++ try { ++ Player player = players.get(i); ++ if (player == null || player.isRemoved() || !player.isAlive()) { ++ continue; ++ } ++ ++ if (conditions.test(level, source, player)) { ++ double dx = player.getX() - x; ++ double dy = player.getY() - y; ++ double dz = player.getZ() - z; ++ double distSq = dx * dx + dy * dy + dz * dz; ++ ++ if (closestDistSq == -1.0 || distSq < closestDistSq) { ++ closestDistSq = distSq; ++ closest = player; ++ } ++ } ++ } catch (IndexOutOfBoundsException e) { ++ break; ++ } catch (Exception e) { ++ System.err.println("Error processing player in findNearestPlayerSafely: " + e.getMessage()); ++ continue; ++ } ++ } ++ ++ return closest; ++ } catch (Exception e) { ++ System.err.println("Error in findNearestPlayerSafely: " + e.getMessage()); ++ return null; ++ } ++ } ++ ++ // Synchronous fallback method ++ private void findTargetSync() { ++ try { ++ ServerLevel serverLevel = getServerLevel(this.mob); ++ if (serverLevel == null) { ++ return; ++ } ++ ++ if (this.targetType != Player.class && this.targetType != ServerPlayer.class) { ++ try { ++ this.target = serverLevel.getNearestEntity( ++ this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true), ++ this.getTargetConditions(), ++ this.mob, ++ this.mob.getX(), ++ this.mob.getEyeY(), ++ this.mob.getZ() ++ ); ++ } catch (Exception e) { ++ System.err.println("Error in sync entity finding: " + e.getMessage()); ++ this.target = null; ++ } ++ } else { ++ try { ++ this.target = serverLevel.getNearestPlayer(this.getTargetConditions(), this.mob, this.mob.getX(), this.mob.getEyeY(), this.mob.getZ()); ++ } catch (Exception e) { ++ System.err.println("Error in sync player finding: " + e.getMessage()); ++ this.target = null; ++ } ++ } ++ } catch (Exception e) { ++ System.err.println("Error in findTargetSync: " + e.getMessage()); ++ this.target = null; ++ } ++ } ++ // Leaf end - Async Target Finding ++ + @Override + public void start() { +- this.mob.setTarget(this.target, this.target instanceof ServerPlayer ? org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY); // CraftBukkit - reason ++ // Leaf start - Async Target Finding ++ LivingEntity targetEntity = this.target; ++ if (targetEntity != null && !targetEntity.isRemoved() && targetEntity.isAlive()) { ++ try { ++ this.mob.setTarget(targetEntity, targetEntity instanceof ServerPlayer ? ++ org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER : ++ org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY); ++ } catch (Exception e) { ++ System.err.println("Error in setTarget: " + e.getMessage()); ++ this.target = null; ++ } ++ } ++ // Leaf end - Async Target Finding + super.start(); + } + diff --git a/leaf-server/minecraft-patches/features/0238-Null-handling-on-MultifaceSpreader.patch b/leaf-server/minecraft-patches/features/0238-Null-handling-on-MultifaceSpreader.patch new file mode 100644 index 00000000..7a4c1157 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0238-Null-handling-on-MultifaceSpreader.patch @@ -0,0 +1,26 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Wed, 2 Apr 2025 23:03:22 +0200 +Subject: [PATCH] Null handling on MultifaceSpreader + +WHYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY + +diff --git a/net/minecraft/world/level/block/MultifaceSpreader.java b/net/minecraft/world/level/block/MultifaceSpreader.java +index 5eb291396a83b8d98294c5f53d2e1f4915a0d84e..b5a78dc342bd2d2eecaaa3ab7cc89e391a91b2da 100644 +--- a/net/minecraft/world/level/block/MultifaceSpreader.java ++++ b/net/minecraft/world/level/block/MultifaceSpreader.java +@@ -148,6 +148,14 @@ public class MultifaceSpreader { + } + + default boolean placeBlock(LevelAccessor level, MultifaceSpreader.SpreadPos pos, BlockState state, boolean markForPostprocessing) { ++ // Leaf start - Null handling on MultifaceSpreader ++ // Check for null ++ if (pos.source() == null || pos.pos() == null) { ++ org.dreeam.leaf.config.LeafConfig.LOGGER.warn("Invalid SpreadPos with null source or position: {}", pos); ++ return false; ++ } ++ // Leaf end - Null handling on MultifaceSpreader ++ + BlockState stateForPlacement = this.getStateForPlacement(state, level, pos.pos(), pos.face()); + if (stateForPlacement != null) { + if (markForPostprocessing) { diff --git a/leaf-server/minecraft-patches/features/0239-More-virtual-threads.patch b/leaf-server/minecraft-patches/features/0239-More-virtual-threads.patch new file mode 100644 index 00000000..bd7e9a31 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0239-More-virtual-threads.patch @@ -0,0 +1,112 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sun, 6 Apr 2025 11:22:35 +0200 +Subject: [PATCH] More virtual threads + + +diff --git a/net/minecraft/Util.java b/net/minecraft/Util.java +index 9918572306e983281d05c6d28c8a5d843348ad2d..1d4ad1370ca041753ce765b1a2feddae59f1f8ca 100644 +--- a/net/minecraft/Util.java ++++ b/net/minecraft/Util.java +@@ -98,7 +98,8 @@ public class Util { + public static final TracingExecutor DIMENSION_DATA_IO_POOL = makeExtraIoExecutor("Dimension-Data-IO-Worker-"); // Paper - Separate dimension data IO pool + private static final TracingExecutor DOWNLOAD_POOL = makeIoExecutor("Download-", true); + // Paper start - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread +- public static final ExecutorService PROFILE_EXECUTOR = Executors.newFixedThreadPool(2, new java.util.concurrent.ThreadFactory() { ++ // Leaf start - More virtual threads ++ public static final ExecutorService PROFILE_EXECUTOR = createProfileExecutor(); /* new Executors.newFixedThreadPool(2, new java.util.concurrent.ThreadFactory() { + + private final AtomicInteger count = new AtomicInteger(); + +@@ -111,7 +112,30 @@ public class Util { + }); + return ret; + } +- }); ++ }); */ ++ ++ private static ExecutorService createProfileExecutor() { ++ final java.util.concurrent.ThreadFactory factory; ++ if (org.dreeam.leaf.config.modules.opt.VT4ProfileExecutor.enabled && org.galemc.gale.virtualthread.VirtualThreadService.isSupported()) { ++ factory = org.galemc.gale.virtualthread.VirtualThreadService.get().createFactory(); ++ } else { ++ factory = new java.util.concurrent.ThreadFactory() { ++ private final AtomicInteger count = new AtomicInteger(); ++ ++ @Override ++ public Thread newThread(Runnable run) { ++ Thread ret = new Thread(run); ++ ret.setName("Profile Lookup Executor #" + this.count.getAndIncrement()); ++ ret.setUncaughtExceptionHandler((Thread thread, Throwable throwable) -> { ++ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable); ++ }); ++ return ret; ++ } ++ }; ++ } ++ return Executors.newFixedThreadPool(2, factory); ++ } ++ // Leaf end - More virtual threads + // Paper end - don't submit BLOCKING PROFILE LOOKUPS to the world gen thread + private static final DateTimeFormatter FILENAME_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss", Locale.ROOT); + public static final int LINEAR_LOOKUP_THRESHOLD = 8; +@@ -255,16 +279,31 @@ public class Util { + } + + private static TracingExecutor makeIoExecutor(String name, boolean daemon) { +- AtomicInteger atomicInteger = new AtomicInteger(1); +- return new TracingExecutor(Executors.newCachedThreadPool(task -> { +- Thread thread = new Thread(task); +- String string = name + atomicInteger.getAndIncrement(); +- TracyClient.setThreadName(string, name.hashCode()); +- thread.setName(string); +- thread.setDaemon(daemon); +- thread.setUncaughtExceptionHandler(Util::onThreadException); +- return thread; +- })); ++ // Leaf start - More virtual threads ++ final java.util.concurrent.ThreadFactory factory; ++ final boolean useVirtualThreads; // Gale - virtual thread support ++ if (name.startsWith("Download-")) { // Gale - virtual thread support ++ useVirtualThreads = org.dreeam.leaf.config.modules.opt.VT4DownloadPool.enabled && org.galemc.gale.virtualthread.VirtualThreadService.isSupported(); // Gale - virtual thread support ++ } else { ++ useVirtualThreads = false; ++ } ++ ++ if (useVirtualThreads) { ++ factory = org.galemc.gale.virtualthread.VirtualThreadService.get().createFactory(); ++ } else { ++ AtomicInteger atomicInteger = new AtomicInteger(1); ++ factory = task -> { ++ Thread thread = new Thread(task); ++ String string = name + atomicInteger.getAndIncrement(); ++ TracyClient.setThreadName(string, name.hashCode()); ++ thread.setName(string); ++ thread.setDaemon(daemon); ++ thread.setUncaughtExceptionHandler(Util::onThreadException); ++ return thread; ++ }; ++ } ++ return new TracingExecutor(Executors.newCachedThreadPool(factory)); ++ // Leaf end - More virtual threads + } + + // Paper start - Separate dimension data IO pool +@@ -1109,7 +1148,7 @@ public class Util { + } + + public static Typed readTypedOrThrow(Type type, Dynamic data, boolean partial) { +- DataResult> dataResult = type.readTyped(data).map(Pair::getFirst); ++ DataResult> dataResult = type.readTyped(data).map(Pair::getFirst); // Paper - Fix generics issue // Gale - Fix generics issue + + try { + return partial ? dataResult.getPartialOrThrow(IllegalStateException::new) : dataResult.getOrThrow(IllegalStateException::new); +@@ -1158,7 +1197,7 @@ public class Util { + } + + public void openUri(URI uri) { +- throw new IllegalStateException("This method is not useful on dedicated servers."); // Paper - Fix warnings on build by removing client-only code ++ // throw new IllegalStateException("This method is not useful on dedicated servers."); // Paper - Fix warnings on build by removing client-only code // Leaf - More virtual threads - This method is not useful on dedicated servers + } + + public void openFile(File file) { diff --git a/leaf-server/paper-patches/features/0048-PlayerInventoryOverflowEvent.patch b/leaf-server/paper-patches/features/0048-PlayerInventoryOverflowEvent.patch index b564209f..465195d7 100644 --- a/leaf-server/paper-patches/features/0048-PlayerInventoryOverflowEvent.patch +++ b/leaf-server/paper-patches/features/0048-PlayerInventoryOverflowEvent.patch @@ -5,17 +5,18 @@ Subject: [PATCH] PlayerInventoryOverflowEvent diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -index 19180c08f41db939c1a9f0caeb62e5beb1117f69..59ab5bd3582cdae351d579719244c4ad28878a00 100644 +index 19180c08f41db939c1a9f0caeb62e5beb1117f69..c765118189cbf8db7291c1d50214a7f31acc3a49 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -@@ -340,6 +340,15 @@ public class CraftInventory implements Inventory { +@@ -340,6 +340,16 @@ public class CraftInventory implements Inventory { } } } + + // Leaf start - PlayerInventoryOverflowEvent -+ if (org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent.getHandlerList().getRegisteredListeners().length > 0 && !leftover.isEmpty() && this.getHolder() instanceof org.bukkit.craftbukkit.entity.CraftPlayer craftPlayer) { -+ new org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent(craftPlayer, leftover).callEvent(); ++ if (org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent.getHandlerList().getRegisteredListeners().length > 0 ++ && !leftover.isEmpty() && this.inventory instanceof net.minecraft.world.entity.player.Inventory && this.inventory.getOwner() instanceof org.bukkit.entity.Player player) { ++ new org.dreeam.leaf.event.player.PlayerInventoryOverflowEvent(player, leftover).callEvent(); + + leftover = new HashMap<>(); + } diff --git a/leaf-server/paper-patches/features/0050-Async-playerdata-saving.patch b/leaf-server/paper-patches/features/0050-Async-playerdata-saving.patch index f6441858..47bdb8e2 100644 --- a/leaf-server/paper-patches/features/0050-Async-playerdata-saving.patch +++ b/leaf-server/paper-patches/features/0050-Async-playerdata-saving.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Async playerdata saving diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -index 1456f2d1a92c8315177fb03d0c7ec943d5f5b097..e70692272aae39ea01fb6860ec4cb703ea531781 100644 +index 1456f2d1a92c8315177fb03d0c7ec943d5f5b097..aadc92b9e82bfe3d65ea8f47ac28ba2d70eb3a7f 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java @@ -199,7 +199,7 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa @@ -17,34 +17,21 @@ index 1456f2d1a92c8315177fb03d0c7ec943d5f5b097..e70692272aae39ea01fb6860ec4cb703 } private CompoundTag getBukkitData() { -@@ -744,6 +744,17 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa +@@ -744,16 +744,7 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa * @param compoundTag */ private void save(CompoundTag compoundTag) { -+ // Leaf start - Async playerdata saving -+ synchronized (server.console.playerDataStorage) { -+ while (server.console.playerDataStorage.savingQueue.contains(getUniqueId())) { -+ try { -+ Thread.sleep(1L); -+ } catch (InterruptedException ignored) { -+ } -+ } -+ server.console.playerDataStorage.savingQueue.add(getUniqueId()); -+ } -+ // Leaf end - Async playerdata saving - File playerDir = server.console.playerDataStorage.getPlayerDir(); - try { - File tempFile = File.createTempFile(this.getUniqueId()+"-", ".dat", playerDir); -@@ -753,6 +764,12 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa - net.minecraft.Util.safeReplaceFile(playerDataFile.toPath(), tempFile.toPath(), playerDataFileOld.toPath()); - } catch (java.io.IOException e) { - e.printStackTrace(); -+ // Leaf start - Async playerdata saving -+ } finally { -+ synchronized (server.console.playerDataStorage) { -+ server.console.playerDataStorage.savingQueue.remove(getUniqueId()); -+ } -+ // Leaf end - Async playerdata saving - } +- File playerDir = server.console.playerDataStorage.getPlayerDir(); +- try { +- File tempFile = File.createTempFile(this.getUniqueId()+"-", ".dat", playerDir); +- net.minecraft.nbt.NbtIo.writeCompressed(compoundTag, tempFile.toPath()); +- File playerDataFile = new File(playerDir, this.getUniqueId()+".dat"); +- File playerDataFileOld = new File(playerDir, this.getUniqueId()+".dat_old"); +- net.minecraft.Util.safeReplaceFile(playerDataFile.toPath(), tempFile.toPath(), playerDataFileOld.toPath()); +- } catch (java.io.IOException e) { +- e.printStackTrace(); +- } ++ server.console.playerDataStorage.save(this.getName(), this.getUniqueId(), this.getUniqueId().toString(), compoundTag); // Leaf - Async playerdata saving } // Purpur end - OfflinePlayer API + } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java b/leaf-server/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java index bb363235..40f552d1 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/AsyncPlayerDataSaving.java @@ -2,7 +2,9 @@ package org.dreeam.leaf.async; import org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave; +import java.util.Optional; 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; @@ -10,7 +12,7 @@ import java.util.concurrent.TimeUnit; public class AsyncPlayerDataSaving { public static final ExecutorService IO_POOL = new ThreadPoolExecutor( - 1, 1, 0, TimeUnit.MILLISECONDS, + 1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new com.google.common.util.concurrent.ThreadFactoryBuilder() .setPriority(Thread.NORM_PRIORITY - 2) @@ -23,11 +25,12 @@ public class AsyncPlayerDataSaving { private AsyncPlayerDataSaving() { } - public static void save(Runnable runnable) { + public static Optional> submit(Runnable runnable) { if (!AsyncPlayerDataSave.enabled) { runnable.run(); + return Optional.empty(); } else { - IO_POOL.execute(runnable); + return Optional.of(IO_POOL.submit(runnable)); } } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncBlockFinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncBlockFinding.java index 2bd57a18..fffd4b3e 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncBlockFinding.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncBlockFinding.java @@ -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 AsyncBlockFinding extends ConfigModules { @@ -10,14 +9,12 @@ public class AsyncBlockFinding extends ConfigModules { return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-block-finding"; } - @Experimental public static boolean enabled = false; public static boolean asyncBlockFindingInitialized; @Override public void onLoaded() { config.addCommentRegionBased(getBasePath(), """ - **Experimental feature** This moves the expensive search calculations to a background thread while keeping the actual block validation on the main thread.""", """ diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncChunkSend.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncChunkSend.java index 2e2dae90..34fae012 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncChunkSend.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncChunkSend.java @@ -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 AsyncChunkSend extends ConfigModules { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java new file mode 100644 index 00000000..961572c5 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncTargetFinding.java @@ -0,0 +1,32 @@ + +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 { + + public String getBasePath() { + return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-target-finding"; + } + + @Experimental + public static boolean enabled = false; + public static boolean asyncTargetFindingInitialized; + + @Override + public void onLoaded() { + config.addCommentRegionBased(getBasePath(), """ + **Experimental feature** + This moves the expensive entity target search calculations to a background thread while + keeping the actual entity validation on the main thread.""", + """ + 这会将昂贵的实体目标搜索计算移至后台线程, 同时在主线程上保持实际的实体验证."""); + + if (!asyncTargetFindingInitialized) { + asyncTargetFindingInitialized = true; + enabled = config.getBoolean(getBasePath() + ".enabled", enabled); + } + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4DownloadPool.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4DownloadPool.java new file mode 100644 index 00000000..22fb0741 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4DownloadPool.java @@ -0,0 +1,21 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class VT4DownloadPool extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName(); + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".use-virtual-thread-for-download-pool", enabled, + config.pickStringRegionBased( + "Use the new Virtual Thread introduced in JDK 21 for download worker pool.", + "是否为下载工作线程池使用虚拟线程(如果可用)。")); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4ProfileExecutor.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4ProfileExecutor.java new file mode 100644 index 00000000..34c04e56 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/VT4ProfileExecutor.java @@ -0,0 +1,21 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class VT4ProfileExecutor extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName(); + } + + public static boolean enabled = true; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath() + ".use-virtual-thread-for-profile-executor", enabled, + config.pickStringRegionBased( + "Use the new Virtual Thread introduced in JDK 21 for profile lookup executor.", + "是否为档案查询执行器使用虚拟线程(如果可用)。")); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/map/StringCanonizingOpenHashMap.java b/leaf-server/src/main/java/org/dreeam/leaf/util/map/StringCanonizingOpenHashMap.java index e55c07c9..d909eddf 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/util/map/StringCanonizingOpenHashMap.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/map/StringCanonizingOpenHashMap.java @@ -10,7 +10,7 @@ import java.util.function.Function; public class StringCanonizingOpenHashMap extends Object2ObjectOpenHashMap { - private static final Interner KEY_INTERNER = Interners.newWeakInterner(); + private static final Interner KEY_INTERNER = Interners.newBuilder().weak().concurrencyLevel(16).build(); private static String intern(String key) { return key != null ? KEY_INTERNER.intern(key) : null; @@ -36,9 +36,10 @@ public class StringCanonizingOpenHashMap extends Object2ObjectOpenHashMap m) { if (m.isEmpty()) return; - Map tmp = new Object2ObjectOpenHashMap<>(m.size()); - m.forEach((k, v) -> tmp.put(intern(k), v)); - super.putAll(tmp); + ensureCapacity(size() + m.size()); + for (Map.Entry entry : m.entrySet()) { + super.put(intern(entry.getKey()), entry.getValue()); + } } private void putWithoutInterning(String key, T value) { @@ -46,7 +47,7 @@ public class StringCanonizingOpenHashMap extends Object2ObjectOpenHashMap StringCanonizingOpenHashMap deepCopy(StringCanonizingOpenHashMap incomingMap, Function deepCopier) { - StringCanonizingOpenHashMap newMap = new StringCanonizingOpenHashMap<>(incomingMap.size(), 0.8f); + StringCanonizingOpenHashMap newMap = new StringCanonizingOpenHashMap<>(incomingMap.size(), incomingMap.f); ObjectIterator> iterator = incomingMap.object2ObjectEntrySet().fastIterator(); while (iterator.hasNext()) { diff --git a/todos.md b/todos.md index 49bda871..cfb775f6 100644 --- a/todos.md +++ b/todos.md @@ -15,4 +15,4 @@ - [ ] Check beacon issues fix - [ ] Update README.md - [ ] Remove stream in Inventory and check new changes -- [ ] Update from Leaf 1.21.4 (curr commit: `1977a5b12c01f915d7c04a765b18957cbfbeeb24`) \ No newline at end of file +- [ ] Update from Leaf 1.21.4 (curr commit: `dd22d9cafffece7d469a7d702f63952abea441d2`) \ No newline at end of file