From da18026680452f312be01736f1ec8f299706aef4 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Sat, 24 May 2025 22:51:55 +0800 Subject: [PATCH 01/25] Remove stream in CraftBlockData toString() --- .github/workflows/publish-api.yml | 4 +- ...Configurable-unknown-command-message.patch | 9 ++-- .../0133-Spawner-Configurations.patch | 7 +-- .../features/0180-Paw-optimization.patch | 52 +++++++++++++++++++ .../features/0038-Paw-optimization.patch | 50 ++++++++++++++++++ 5 files changed, 112 insertions(+), 10 deletions(-) create mode 100644 leaf-server/paper-patches/features/0038-Paw-optimization.patch diff --git a/.github/workflows/publish-api.yml b/.github/workflows/publish-api.yml index ea6c0561..8c4f2d15 100644 --- a/.github/workflows/publish-api.yml +++ b/.github/workflows/publish-api.yml @@ -42,8 +42,8 @@ jobs: --build-cache \ --no-daemon - - name: Create MojmapPaperclipJar - run: ./gradlew createMojmapPaperclipJar --stacktrace --parallel --no-daemon + - name: Build + run: ./gradlew build -x test - name: Publish Maven API continue-on-error: true diff --git a/leaf-server/minecraft-patches/features/0055-Configurable-unknown-command-message.patch b/leaf-server/minecraft-patches/features/0055-Configurable-unknown-command-message.patch index 3339f433..7c834912 100644 --- a/leaf-server/minecraft-patches/features/0055-Configurable-unknown-command-message.patch +++ b/leaf-server/minecraft-patches/features/0055-Configurable-unknown-command-message.patch @@ -5,15 +5,14 @@ Subject: [PATCH] Configurable unknown command message diff --git a/net/minecraft/commands/Commands.java b/net/minecraft/commands/Commands.java -index 8b9374ee6df71228bb8ea22661622a15cf3bc350..59e5f93df54abc56329b38340882dade7d7104a3 100644 +index 8b9374ee6df71228bb8ea22661622a15cf3bc350..b44544bdea5582bfef3ff30bfd196b80258827cc 100644 --- a/net/minecraft/commands/Commands.java +++ b/net/minecraft/commands/Commands.java -@@ -404,31 +404,9 @@ public class Commands { +@@ -404,31 +404,8 @@ public class Commands { // Paper start - Add UnknownCommandEvent final net.kyori.adventure.text.TextComponent.Builder builder = net.kyori.adventure.text.Component.text(); // source.sendFailure(ComponentUtils.fromMessage(var7.getRawMessage())); - builder.color(net.kyori.adventure.text.format.NamedTextColor.RED).append(io.papermc.paper.command.brigadier.MessageComponentSerializer.message().deserialize(var7.getRawMessage())); -+ final net.kyori.adventure.text.Component message = getUnknownCommandMessage(builder, var7, label); // Leaf - Configurable unknown command message // Paper end - Add UnknownCommandEvent - if (var7.getInput() != null && var7.getCursor() >= 0) { - int min = Math.min(var7.getInput().length(), var7.getCursor()); @@ -38,11 +37,11 @@ index 8b9374ee6df71228bb8ea22661622a15cf3bc350..59e5f93df54abc56329b38340882dade - .append(io.papermc.paper.adventure.PaperAdventure.asAdventure(mutableComponent)); - } - org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(source.getBukkitSender(), command, org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty() ? null : builder.build()); -+ org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(source.getBukkitSender(), command, message); // Leaf - Configurable unknown command message ++ org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(source.getBukkitSender(), command, getUnknownCommandMessage(builder, var7, label)); // Leaf - Configurable unknown command message org.bukkit.Bukkit.getServer().getPluginManager().callEvent(event); if (event.message() != null) { source.sendFailure(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false); -@@ -680,6 +658,92 @@ public class Commands { +@@ -680,6 +657,92 @@ public class Commands { }; } diff --git a/leaf-server/minecraft-patches/features/0133-Spawner-Configurations.patch b/leaf-server/minecraft-patches/features/0133-Spawner-Configurations.patch index 540bfe40..5825fe36 100644 --- a/leaf-server/minecraft-patches/features/0133-Spawner-Configurations.patch +++ b/leaf-server/minecraft-patches/features/0133-Spawner-Configurations.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Spawner Configurations diff --git a/net/minecraft/world/level/BaseSpawner.java b/net/minecraft/world/level/BaseSpawner.java -index 8c6f8cb08b247dcf497822ae991aa3afbcb784f1..fd1c151958ec2535e56d2d04938803d586eeca8e 100644 +index 8c6f8cb08b247dcf497822ae991aa3afbcb784f1..48da8ab270b34edb15fb03123171c08522535412 100644 --- a/net/minecraft/world/level/BaseSpawner.java +++ b/net/minecraft/world/level/BaseSpawner.java @@ -53,6 +53,12 @@ public abstract class BaseSpawner { @@ -119,7 +119,7 @@ index 8c6f8cb08b247dcf497822ae991aa3afbcb784f1..fd1c151958ec2535e56d2d04938803d5 int size1 = serverLevel.getEntities( EntityTypeTest.forExactClass(entity.getClass()), new AABB(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1).inflate(this.spawnRange), -@@ -156,12 +217,28 @@ public abstract class BaseSpawner { +@@ -156,12 +217,29 @@ public abstract class BaseSpawner { this.delay(serverLevel, pos); return; } @@ -147,10 +147,11 @@ index 8c6f8cb08b247dcf497822ae991aa3afbcb784f1..fd1c151958ec2535e56d2d04938803d5 + } + + if (mobSpecificRulesFailed || obstructionFailed) { ++ // Leaf end - Spawner Configurations continue; } -@@ -249,6 +326,13 @@ public abstract class BaseSpawner { +@@ -249,6 +327,13 @@ public abstract class BaseSpawner { this.spawnPotentials = SimpleWeightedRandomList.single(this.nextSpawnData != null ? this.nextSpawnData : new SpawnData()); } diff --git a/leaf-server/minecraft-patches/features/0180-Paw-optimization.patch b/leaf-server/minecraft-patches/features/0180-Paw-optimization.patch index 10785958..5e1b9fb8 100644 --- a/leaf-server/minecraft-patches/features/0180-Paw-optimization.patch +++ b/leaf-server/minecraft-patches/features/0180-Paw-optimization.patch @@ -212,6 +212,58 @@ index 64f24d3e0ecb91e0b4df6229354aeac549234f1b..df23d80d6b18e900414aa02e5c1812f0 int floor = Mth.floor(x); int floor1 = Mth.floor(y); int floor2 = Mth.floor(z); +diff --git a/net/minecraft/world/level/block/state/StateHolder.java b/net/minecraft/world/level/block/state/StateHolder.java +index 098518383d2c07491e047749ce3a834e98b85b1d..3dea58dff1dea05bd4a052684cb7978b2a63e452 100644 +--- a/net/minecraft/world/level/block/state/StateHolder.java ++++ b/net/minecraft/world/level/block/state/StateHolder.java +@@ -25,7 +25,7 @@ public abstract class StateHolder implements ca.spottedleaf.moonrise.patch + return ""; + } else { + Property property = propertyEntry.getKey(); +- return property.getName() + "=" + this.getName(property, propertyEntry.getValue()); ++ return property.getName() + "=" + this.getName(property, propertyEntry.getValue()); // Leaf - paw optimization - diff on change + } + } + +@@ -73,13 +73,37 @@ public abstract class StateHolder implements ca.spottedleaf.moonrise.patch + stringBuilder.append(this.owner); + if (!this.getValues().isEmpty()) { + stringBuilder.append('['); +- stringBuilder.append(this.getValues().entrySet().stream().map(PROPERTY_ENTRY_TO_STRING_FUNCTION).collect(Collectors.joining(","))); ++ // Leaf start - paw optimization ++ int i = 0; ++ for (Map.Entry, Comparable> propertyEntry : this.getValues().entrySet()) { ++ if (propertyEntry == null) { ++ stringBuilder.append(""); ++ } else { ++ Property property = propertyEntry.getKey(); ++ Comparable value = propertyEntry.getValue(); ++ ++ stringBuilder.append(property.getName()).append("=").append(getValueName(property, value)); ++ } ++ ++ if (i < this.getValues().size() - 1) { ++ stringBuilder.append(","); ++ } ++ ++ i++; ++ } ++ // Leaf end - paw optimization + stringBuilder.append(']'); + } + + return stringBuilder.toString(); + } + ++ // Leaf start - paw optimization ++ private > String getValueName(Property property, Comparable value) { ++ return property.getName((T) value); ++ } ++ // Leaf end - paw optimization ++ + public Collection> getProperties() { + return this.optimisedTable.getProperties(); // Paper - optimise blockstate property access + } diff --git a/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java b/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java index 03ec2264b19e1794b609fe09d1ceaba4e0c4d669..3f38fe0140d13c7c356340ba06b55469ede0a1ad 100644 --- a/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java diff --git a/leaf-server/paper-patches/features/0038-Paw-optimization.patch b/leaf-server/paper-patches/features/0038-Paw-optimization.patch new file mode 100644 index 00000000..911599a4 --- /dev/null +++ b/leaf-server/paper-patches/features/0038-Paw-optimization.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Thu, 24 Apr 2025 16:36:16 -0400 +Subject: [PATCH] Paw optimization + +Some random optimizations + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +index 66e40149a59c4bd5d5ca3b5a69c733eea49fb86b..2b11dc4750bc1f341c4c55145c42add22d0e9562 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +@@ -244,13 +244,37 @@ public class CraftBlockData implements BlockData { + + if (!states.isEmpty()) { + stateString.append('['); +- stateString.append(states.entrySet().stream().map(StateHolder.PROPERTY_ENTRY_TO_STRING_FUNCTION).collect(Collectors.joining(","))); ++ // Leaf start - paw optimization ++ int i = 0; ++ for (Map.Entry, Comparable> propertyEntry : states.entrySet()) { ++ if (propertyEntry == null) { ++ stateString.append(""); ++ } else { ++ Property property = propertyEntry.getKey(); ++ Comparable value = propertyEntry.getValue(); ++ ++ stateString.append(property.getName()).append("=").append(getValueName(property, value)); ++ } ++ ++ if (i < states.size() - 1) { ++ stateString.append(","); ++ } ++ ++ i++; ++ } ++ // Leaf end - paw optimization + stateString.append(']'); + } + + return stateString.toString(); + } + ++ // Leaf start - paw optimization ++ private > String getValueName(Property property, Comparable value) { ++ return property.getName((T) value); ++ } ++ // Leaf end - paw optimization ++ + public Map toStates(boolean hideUnspecified) { + return (hideUnspecified && this.parsedStates != null) ? CraftBlockData.toStates(this.parsedStates) : CraftBlockData.toStates(this.state.getValues()); + } From 2d0561db5fc182e60a063e65d36906343451fb08 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Mon, 26 May 2025 01:19:25 +0800 Subject: [PATCH 02/25] [ci skip] Move some useless patches from work to removed --- ...ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch | 1 + ...50-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch | 1 + .../features/0169-Only-tick-items-at-hand.patch | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) rename leaf-archived-patches/{work => removed/hardfork}/server/0124-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch (94%) rename leaf-archived-patches/{work => removed/legacy}/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch (99%) diff --git a/leaf-archived-patches/work/server/0124-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch b/leaf-archived-patches/removed/hardfork/server/0124-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch similarity index 94% rename from leaf-archived-patches/work/server/0124-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch rename to leaf-archived-patches/removed/hardfork/server/0124-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch index 36b4be10..ffea6692 100644 --- a/leaf-archived-patches/work/server/0124-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch +++ b/leaf-archived-patches/removed/hardfork/server/0124-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch @@ -4,6 +4,7 @@ Date: Thu, 1 Aug 2024 00:43:05 +0900 Subject: [PATCH] ShreddedPaper: Don't block main thread in Connection#syncAfterConfigurationChange +Removed since Leaf 1.21.4, replaced by async config switch diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java index 00a82873d226f113278632a53c0faca420dd67d4..5b46036868b6c9d082e35591e58735e16adaae62 100644 diff --git a/leaf-archived-patches/work/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch b/leaf-archived-patches/removed/legacy/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch similarity index 99% rename from leaf-archived-patches/work/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch rename to leaf-archived-patches/removed/legacy/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch index 5bd6c56d..fcaba870 100644 --- a/leaf-archived-patches/work/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch +++ b/leaf-archived-patches/removed/legacy/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch @@ -3,6 +3,7 @@ From: Taiyou06 Date: Fri, 8 Nov 2024 00:54:42 +0100 Subject: [PATCH] Use MCUtil.asyncExecutor for MAIN_WORKER_EXECUTOR +Removed since Leaf 1.21.3 diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java index 815253d03b85a7a476c1efdeca9496fd64afc137..bb4c9bbebaefe9a0c7d213e9b2b07308e684dc7c 100644 diff --git a/leaf-server/minecraft-patches/features/0169-Only-tick-items-at-hand.patch b/leaf-server/minecraft-patches/features/0169-Only-tick-items-at-hand.patch index 0adbe382..c2f62aee 100644 --- a/leaf-server/minecraft-patches/features/0169-Only-tick-items-at-hand.patch +++ b/leaf-server/minecraft-patches/features/0169-Only-tick-items-at-hand.patch @@ -29,7 +29,7 @@ index 50cf63666071f5d01a85dfc6c6c45c19b05d8ec2..ef53b9f307572dd5dc99d02e017d6b2d if (this.getHealth() != this.lastSentHealth || this.lastSentFood != this.foodData.getFoodLevel() diff --git a/net/minecraft/world/entity/player/Player.java b/net/minecraft/world/entity/player/Player.java -index 8469cf9cacebea3fdd855f6c3e9e2cf61c78b8ac..d39757fae68580abcb13804e01587e542f1e087d 100644 +index 8469cf9cacebea3fdd855f6c3e9e2cf61c78b8ac..76d85632e1364e71a2aa15dbef41c62422ffd7af 100644 --- a/net/minecraft/world/entity/player/Player.java +++ b/net/minecraft/world/entity/player/Player.java @@ -631,7 +631,14 @@ public abstract class Player extends LivingEntity { @@ -39,7 +39,7 @@ index 8469cf9cacebea3fdd855f6c3e9e2cf61c78b8ac..d39757fae68580abcb13804e01587e54 + // Leaf start - Only tick items at hand + if (org.dreeam.leaf.config.modules.opt.OptimizeItemTicking.onlyTickItemsInHand) { + this.getMainHandItem().inventoryTick(this.level(), this, 0, true); -+ this.getOffhandItem().inventoryTick(this.level(), this, 0, true); ++ this.getOffhandItem().inventoryTick(this.level(), this, 1, true); + } else { this.inventory.tick(); + } From ec6a7f35ef7ffdf190b66b5353be39f1086b6243 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Mon, 26 May 2025 01:31:22 +0800 Subject: [PATCH 03/25] [ci skip] Remove some --- .../server/0066-Moonrise-Optimise-checkInsideBlocks.patch | 2 ++ ...7-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch | 2 ++ 2 files changed, 4 insertions(+) rename leaf-archived-patches/{work => removed/legacy}/server/0066-Moonrise-Optimise-checkInsideBlocks.patch (99%) rename leaf-archived-patches/{work => removed/legacy}/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch (99%) diff --git a/leaf-archived-patches/work/server/0066-Moonrise-Optimise-checkInsideBlocks.patch b/leaf-archived-patches/removed/legacy/server/0066-Moonrise-Optimise-checkInsideBlocks.patch similarity index 99% rename from leaf-archived-patches/work/server/0066-Moonrise-Optimise-checkInsideBlocks.patch rename to leaf-archived-patches/removed/legacy/server/0066-Moonrise-Optimise-checkInsideBlocks.patch index a744fdeb..b2ea1d47 100644 --- a/leaf-archived-patches/work/server/0066-Moonrise-Optimise-checkInsideBlocks.patch +++ b/leaf-archived-patches/removed/legacy/server/0066-Moonrise-Optimise-checkInsideBlocks.patch @@ -3,6 +3,8 @@ From: Spottedleaf Date: Thu, 5 Sep 2024 15:42:15 -0700 Subject: [PATCH] Moonrise: Optimise checkInsideBlocks +Removed since Leaf 1.21.3, Optimized in Minecraft + Original license: GPLv3 Original project: https://github.com/Tuinity/Moonrise diff --git a/leaf-archived-patches/work/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch b/leaf-archived-patches/removed/legacy/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch similarity index 99% rename from leaf-archived-patches/work/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch rename to leaf-archived-patches/removed/legacy/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch index 06f5f781..71a0b9ff 100644 --- a/leaf-archived-patches/work/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch +++ b/leaf-archived-patches/removed/legacy/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch @@ -3,6 +3,8 @@ From: Spottedleaf Date: Thu, 5 Sep 2024 16:23:04 -0700 Subject: [PATCH] Moonrise: Avoid streams for block retrieval in Entity#move +Removed since Leaf 1.21.3, Optimized in Minecraft + Original license: GPLv3 Original project: https://github.com/Tuinity/Moonrise From 705973157c4f02c786de0ecbb30a9ee6ba5dc488 Mon Sep 17 00:00:00 2001 From: Creeam <102713261+HaHaWTH@users.noreply.github.com> Date: Mon, 26 May 2025 09:10:49 +1400 Subject: [PATCH 04/25] Fix Winds-Studio/Leaf#338 (#339) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delayed init, registry太神秘了 --- .../features/0181-Cache-block-path-type.patch | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0181-Cache-block-path-type.patch b/leaf-server/minecraft-patches/features/0181-Cache-block-path-type.patch index 87f5212b..9b11a321 100644 --- a/leaf-server/minecraft-patches/features/0181-Cache-block-path-type.patch +++ b/leaf-server/minecraft-patches/features/0181-Cache-block-path-type.patch @@ -4,18 +4,19 @@ Date: Fri, 23 May 2025 12:01:42 +0900 Subject: [PATCH] Cache block path type -diff --git a/net/minecraft/server/Bootstrap.java b/net/minecraft/server/Bootstrap.java -index 35b91f4daba4ac9751fa388d9da7d127db1356b0..60cc058db9227a61cc3186003efefc4888e06ddc 100644 ---- a/net/minecraft/server/Bootstrap.java -+++ b/net/minecraft/server/Bootstrap.java -@@ -60,6 +60,7 @@ public class Bootstrap { - io.papermc.paper.world.worldgen.OptionallyFlatBedrockConditionSource.bootstrap(); // Paper - Flat bedrock generator settings - }); - // Paper end -+ net.minecraft.world.level.block.Blocks.initPathType(); // Leaf - Cache path type - CreativeModeTabs.validate(); - wrapStreams(); - bootstrapDuration.set(Duration.between(instant, Instant.now()).toMillis()); +diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java +index a54f9030c81a2eb36f4dae951b09a9a6057be936..0b5858ae29269fb9f8516aaa95941ebb4d4915e4 100644 +--- a/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -418,7 +418,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + } + + if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) mobSpawnExecutor.start(); // Pufferfish +- ++ net.minecraft.world.level.block.Blocks.initPathType(); // Leaf - Cache path type + org.purpurmc.purpur.task.BossBarTask.startAll(); // Purpur - Implement TPSBar + if (org.purpurmc.purpur.PurpurConfig.beeCountPayload) org.purpurmc.purpur.task.BeehiveTask.instance().register(); // Purpur - Give bee counts in beehives to Purpur clients + return true; diff --git a/net/minecraft/world/level/block/Blocks.java b/net/minecraft/world/level/block/Blocks.java index 07a8fbfa7eb6e684ea699f009ce2d19311994e39..c5840aad8b6a719873b06a6c5e30cba35555656f 100644 --- a/net/minecraft/world/level/block/Blocks.java From f253f8163b12971d2fa1e63081c4e5472ab6f680 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Mon, 26 May 2025 14:12:49 +0800 Subject: [PATCH 05/25] Add back Rail Optimization: optimized PoweredRailBlock logic --- build-data/leaf.at | 1 + ...ion-optimized-PoweredRailBlock-logic.patch | 405 ------------------ ...0007-Purpur-Server-Minecraft-Changes.patch | 4 +- ...ion-optimized-PoweredRailBlock-logic.patch | 30 ++ .../modules/opt/OptimizedPoweredRails.java | 18 + .../leaf/optimize/OptimizedPoweredRails.java | 333 ++++++++++++++ 6 files changed, 384 insertions(+), 407 deletions(-) delete mode 100644 leaf-archived-patches/work/server/0024-Rail-Optimization-optimized-PoweredRailBlock-logic.patch create mode 100644 leaf-server/minecraft-patches/features/0183-Rail-Optimization-optimized-PoweredRailBlock-logic.patch create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizedPoweredRails.java create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/optimize/OptimizedPoweredRails.java diff --git a/build-data/leaf.at b/build-data/leaf.at index b04d590a..5889428b 100644 --- a/build-data/leaf.at +++ b/build-data/leaf.at @@ -14,6 +14,7 @@ public net.minecraft.world.entity.decoration.ArmorStand noTickEquipmentDirty public net.minecraft.world.entity.monster.Shulker MAX_SCALE public net.minecraft.world.entity.player.Player canGlide()Z public net.minecraft.world.item.CrossbowItem getShotPitch(Lnet/minecraft/util/RandomSource;I)F +public net.minecraft.world.level.block.PoweredRailBlock findPoweredRailSignal(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;ZI)Z public net.minecraft.world.level.block.entity.FuelValues values public net.minecraft.world.level.chunk.storage.RegionFile getOversizedData(II)Lnet/minecraft/nbt/CompoundTag; public net.minecraft.world.level.chunk.storage.RegionFile isOversized(II)Z diff --git a/leaf-archived-patches/work/server/0024-Rail-Optimization-optimized-PoweredRailBlock-logic.patch b/leaf-archived-patches/work/server/0024-Rail-Optimization-optimized-PoweredRailBlock-logic.patch deleted file mode 100644 index e0f434ec..00000000 --- a/leaf-archived-patches/work/server/0024-Rail-Optimization-optimized-PoweredRailBlock-logic.patch +++ /dev/null @@ -1,405 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Sat, 17 Feb 2024 17:57:08 -0500 -Subject: [PATCH] Rail Optimization: optimized PoweredRailBlock logic - -Original project: https://github.com/FxMorin/RailOptimization - -Full Rewrite of the powered rail iteration logic -that makes powered/activator rails turning on/off up to 4x faster. -This rewrite brings a massive performance boost while keeping the vanilla order. This is achieved by running all the -powered rail logic from a single rail instead of each block iterating separately. Which was not only very -expensive but also completely unnecessary and with a lot of massive overhead - -diff --git a/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java b/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java -index bd14c08defe8afc5ceca59d16a5b1dbad178f594..b37ab12d4c51aca1576a14147a959188031d0fd7 100644 ---- a/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java -@@ -29,7 +29,7 @@ public class PoweredRailBlock extends BaseRailBlock { - this.registerDefaultState((BlockState) ((BlockState) ((BlockState) ((BlockState) this.stateDefinition.any()).setValue(PoweredRailBlock.SHAPE, RailShape.NORTH_SOUTH)).setValue(PoweredRailBlock.POWERED, false)).setValue(PoweredRailBlock.WATERLOGGED, false)); - } - -- protected boolean findPoweredRailSignal(Level world, BlockPos pos, BlockState state, boolean flag, int distance) { -+ public boolean findPoweredRailSignal(Level world, BlockPos pos, BlockState state, boolean flag, int distance) { // Leaf - Rail Optimization - protected -> public - if (distance >= world.purpurConfig.railActivationRange) { // Purpur - return false; - } else { -@@ -117,6 +117,12 @@ public class PoweredRailBlock extends BaseRailBlock { - - @Override - protected void updateState(BlockState state, Level world, BlockPos pos, Block neighbor) { -+ // Leaf start - Rail Optimization -+ if (org.dreeam.leaf.config.modules.opt.OptimizedPoweredRails.enabled) { -+ org.dreeam.leaf.optimize.OptimizedPoweredRails.customUpdateState(this, state, world, pos); -+ return; -+ } -+ // Leaf end - Rail Optimization - boolean flag = (Boolean) state.getValue(PoweredRailBlock.POWERED); - boolean flag1 = world.hasNeighborSignal(pos) || this.findPoweredRailSignal(world, pos, state, true, 0) || this.findPoweredRailSignal(world, pos, state, false, 0); - -diff --git a/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizedPoweredRails.java b/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizedPoweredRails.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ded3c385fcfc6c35086f76bed1b2223a6a29a43e ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizedPoweredRails.java -@@ -0,0 +1,18 @@ -+package org.dreeam.leaf.config.modules.opt; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class OptimizedPoweredRails extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.PERF.getBaseKeyName() + ".optimized-powered-rails"; -+ } -+ -+ public static boolean enabled = true; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config().getBoolean(getBasePath(), enabled); -+ } -+} -diff --git a/src/main/java/org/dreeam/leaf/optimize/OptimizedPoweredRails.java b/src/main/java/org/dreeam/leaf/optimize/OptimizedPoweredRails.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9e8bc27585e204aa2df77a90418bbe9e00bcd040 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/optimize/OptimizedPoweredRails.java -@@ -0,0 +1,335 @@ -+package org.dreeam.leaf.optimize; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.PoweredRailBlock; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.block.state.properties.RailShape; -+ -+import java.util.HashMap; -+ -+import static net.minecraft.world.level.block.Block.*; -+import static net.minecraft.world.level.block.PoweredRailBlock.POWERED; -+import static net.minecraft.world.level.block.PoweredRailBlock.SHAPE; -+ -+public class OptimizedPoweredRails { -+ -+ private static final Direction[] EAST_WEST_DIR = new Direction[]{Direction.WEST, Direction.EAST}; -+ private static final Direction[] NORTH_SOUTH_DIR = new Direction[]{Direction.SOUTH, Direction.NORTH}; -+ -+ private static final int UPDATE_FORCE_PLACE = UPDATE_MOVE_BY_PISTON | UPDATE_KNOWN_SHAPE | UPDATE_CLIENTS; -+ -+ public static int RAIL_POWER_LIMIT = 8; -+ -+ public static void giveShapeUpdate(Level level, BlockState state, BlockPos pos, BlockPos fromPos, Direction direction) { -+ BlockState oldState = level.getBlockState(pos); -+ Block.updateOrDestroy( -+ oldState, -+ oldState.updateShape(direction.getOpposite(), state, level, pos, fromPos), -+ level, -+ pos, -+ UPDATE_CLIENTS & -34, -+ 0 -+ ); -+ } -+ -+ public static void setRailPowerLimit(int powerLimit) { -+ RAIL_POWER_LIMIT = powerLimit; -+ } -+ -+ public static void customUpdateState(PoweredRailBlock self, BlockState state, Level level, BlockPos pos) { -+ boolean shouldBePowered = level.hasNeighborSignal(pos) || -+ self.findPoweredRailSignal(level, pos, state, true, 0) || -+ self.findPoweredRailSignal(level, pos, state, false, 0); -+ if (shouldBePowered != state.getValue(POWERED)) { -+ RailShape railShape = state.getValue(SHAPE); -+ if (railShape.isAscending()) { -+ level.setBlock(pos, state.setValue(POWERED, shouldBePowered), 3); -+ level.updateNeighborsAtExceptFromFacing(pos.below(), self, Direction.UP); -+ level.updateNeighborsAtExceptFromFacing(pos.above(), self, Direction.DOWN); //isAscending -+ } else if (shouldBePowered) { -+ powerLane(self, level, pos, state, railShape); -+ } else { -+ dePowerLane(self, level, pos, state, railShape); -+ } -+ } -+ } -+ -+ public static boolean findPoweredRailSignalFaster(PoweredRailBlock self, Level world, BlockPos pos, -+ boolean bl, int distance, RailShape shape, -+ HashMap checkedPos) { -+ BlockState blockState = world.getBlockState(pos); -+ boolean speedCheck = checkedPos.containsKey(pos) && checkedPos.get(pos); -+ if (speedCheck) { -+ return world.hasNeighborSignal(pos) || -+ findPoweredRailSignalFaster(self, world, pos, blockState, bl, distance + 1, checkedPos); -+ } else { -+ if (blockState.is(self)) { -+ RailShape railShape = blockState.getValue(SHAPE); -+ if (shape == RailShape.EAST_WEST && ( -+ railShape == RailShape.NORTH_SOUTH || -+ railShape == RailShape.ASCENDING_NORTH || -+ railShape == RailShape.ASCENDING_SOUTH -+ ) || shape == RailShape.NORTH_SOUTH && ( -+ railShape == RailShape.EAST_WEST || -+ railShape == RailShape.ASCENDING_EAST || -+ railShape == RailShape.ASCENDING_WEST -+ )) { -+ return false; -+ } else if (blockState.getValue(POWERED)) { -+ return world.hasNeighborSignal(pos) || -+ findPoweredRailSignalFaster(self, world, pos, blockState, bl, distance + 1, checkedPos); -+ } else { -+ return false; -+ } -+ } -+ return false; -+ } -+ } -+ -+ public static boolean findPoweredRailSignalFaster(PoweredRailBlock self, Level level, -+ BlockPos pos, BlockState state, boolean bl, int distance, -+ HashMap checkedPos) { -+ if (distance >= RAIL_POWER_LIMIT - 1) return false; -+ int i = pos.getX(); -+ int j = pos.getY(); -+ int k = pos.getZ(); -+ boolean bl2 = true; -+ RailShape railShape = state.getValue(SHAPE); -+ switch (railShape.ordinal()) { -+ case 0 -> { -+ if (bl) ++k; -+ else --k; -+ } -+ case 1 -> { -+ if (bl) --i; -+ else ++i; -+ } -+ case 2 -> { -+ if (bl) { -+ --i; -+ } else { -+ ++i; -+ ++j; -+ bl2 = false; -+ } -+ railShape = RailShape.EAST_WEST; -+ } -+ case 3 -> { -+ if (bl) { -+ --i; -+ ++j; -+ bl2 = false; -+ } else { -+ ++i; -+ } -+ railShape = RailShape.EAST_WEST; -+ } -+ case 4 -> { -+ if (bl) { -+ ++k; -+ } else { -+ --k; -+ ++j; -+ bl2 = false; -+ } -+ railShape = RailShape.NORTH_SOUTH; -+ } -+ case 5 -> { -+ if (bl) { -+ ++k; -+ ++j; -+ bl2 = false; -+ } else { -+ --k; -+ } -+ railShape = RailShape.NORTH_SOUTH; -+ } -+ } -+ return findPoweredRailSignalFaster( -+ self, level, new BlockPos(i, j, k), -+ bl, distance, railShape, checkedPos -+ ) || -+ (bl2 && findPoweredRailSignalFaster( -+ self, level, new BlockPos(i, j - 1, k), -+ bl, distance, railShape, checkedPos -+ )); -+ } -+ -+ public static void powerLane(PoweredRailBlock self, Level world, BlockPos pos, -+ BlockState mainState, RailShape railShape) { -+ world.setBlock(pos, mainState.setValue(POWERED, true), UPDATE_FORCE_PLACE); -+ HashMap checkedPos = new HashMap<>(); -+ checkedPos.put(pos, true); -+ int[] count = new int[2]; -+ if (railShape == RailShape.NORTH_SOUTH) { //Order: +z, -z -+ for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) { -+ setRailPositionsPower(self, world, pos, checkedPos, count, i, NORTH_SOUTH_DIR[i]); -+ } -+ updateRails(self, false, world, pos, mainState, count); -+ } else if (railShape == RailShape.EAST_WEST) { //Order: -x, +x -+ for (int i = 0; i < EAST_WEST_DIR.length; ++i) { -+ setRailPositionsPower(self, world, pos, checkedPos, count, i, EAST_WEST_DIR[i]); -+ } -+ updateRails(self, true, world, pos, mainState, count); -+ } -+ } -+ -+ public static void dePowerLane(PoweredRailBlock self, Level world, BlockPos pos, -+ BlockState mainState, RailShape railShape) { -+ world.setBlock(pos, mainState.setValue(POWERED, false), UPDATE_FORCE_PLACE); -+ int[] count = new int[2]; -+ if (railShape == RailShape.NORTH_SOUTH) { //Order: +z, -z -+ for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) { -+ setRailPositionsDePower(self, world, pos, count, i, NORTH_SOUTH_DIR[i]); -+ } -+ updateRails(self, false, world, pos, mainState, count); -+ } else if (railShape == RailShape.EAST_WEST) { //Order: -x, +x -+ for (int i = 0; i < EAST_WEST_DIR.length; ++i) { -+ setRailPositionsDePower(self, world, pos, count, i, EAST_WEST_DIR[i]); -+ } -+ updateRails(self, true, world, pos, mainState, count); -+ } -+ } -+ -+ private static void setRailPositionsPower(PoweredRailBlock self, Level world, BlockPos pos, -+ HashMap checkedPos, int[] count, int i, Direction dir) { -+ for (int z = 1; z < RAIL_POWER_LIMIT; z++) { -+ BlockPos newPos = pos.relative(dir, z); -+ BlockState state = world.getBlockState(newPos); -+ if (checkedPos.containsKey(newPos)) { -+ if (!checkedPos.get(newPos)) break; -+ count[i]++; -+ } else if (!state.is(self) || state.getValue(POWERED) || !( -+ world.hasNeighborSignal(newPos) || -+ findPoweredRailSignalFaster(self, world, newPos, state, true, 0, checkedPos) || -+ findPoweredRailSignalFaster(self, world, newPos, state, false, 0, checkedPos) -+ )) { -+ checkedPos.put(newPos, false); -+ break; -+ } else { -+ checkedPos.put(newPos, true); -+ world.setBlock(newPos, state.setValue(POWERED, true), UPDATE_FORCE_PLACE); -+ count[i]++; -+ } -+ } -+ } -+ -+ private static void setRailPositionsDePower(PoweredRailBlock self, Level world, BlockPos pos, -+ int[] count, int i, Direction dir) { -+ for (int z = 1; z < RAIL_POWER_LIMIT; z++) { -+ BlockPos newPos = pos.relative(dir, z); -+ BlockState state = world.getBlockState(newPos); -+ if (!state.is(self) || !state.getValue(POWERED) || world.hasNeighborSignal(newPos) || -+ self.findPoweredRailSignal(world, newPos, state, true, 0) || -+ self.findPoweredRailSignal(world, newPos, state, false, 0)) break; -+ world.setBlock(newPos, state.setValue(POWERED, false), UPDATE_FORCE_PLACE); -+ count[i]++; -+ } -+ } -+ -+ private static void shapeUpdateEnd(PoweredRailBlock self, Level world, BlockPos pos, BlockState mainState, -+ int endPos, Direction direction, int currentPos, BlockPos blockPos) { -+ if (currentPos == endPos) { -+ BlockPos newPos = pos.relative(direction, currentPos + 1); -+ OptimizedPoweredRails.giveShapeUpdate(world, mainState, newPos, pos, direction); -+ BlockState state = world.getBlockState(blockPos); -+ if (state.is(self) && state.getValue(SHAPE).isAscending()) -+ OptimizedPoweredRails.giveShapeUpdate(world, mainState, newPos.above(), pos, direction); -+ } -+ } -+ -+ private static void neighborUpdateEnd(PoweredRailBlock self, Level world, BlockPos pos, int endPos, -+ Direction direction, Block block, int currentPos, BlockPos blockPos) { -+ if (currentPos == endPos) { -+ BlockPos newPos = pos.relative(direction, currentPos + 1); -+ world.neighborChanged(newPos, block, pos); -+ BlockState state = world.getBlockState(blockPos); -+ if (state.is(self) && state.getValue(SHAPE).isAscending()) -+ world.neighborChanged(newPos.above(), block, blockPos); -+ } -+ } -+ -+ private static void updateRailsSectionEastWestShape(PoweredRailBlock self, Level world, BlockPos pos, -+ int c, BlockState mainState, Direction dir, -+ int[] count, int countAmt) { -+ BlockPos pos1 = pos.relative(dir, c); -+ if (c == 0 && count[1] == 0) -+ giveShapeUpdate(world, mainState, pos1.relative(dir.getOpposite()), pos, dir.getOpposite()); -+ shapeUpdateEnd(self, world, pos, mainState, countAmt, dir, c, pos1); -+ giveShapeUpdate(world, mainState, pos1.below(), pos, Direction.DOWN); -+ giveShapeUpdate(world, mainState, pos1.above(), pos, Direction.UP); -+ giveShapeUpdate(world, mainState, pos1.north(), pos, Direction.NORTH); -+ giveShapeUpdate(world, mainState, pos1.south(), pos, Direction.SOUTH); -+ } -+ -+ private static void updateRailsSectionNorthSouthShape(PoweredRailBlock self, Level world, BlockPos pos, -+ int c, BlockState mainState, Direction dir, -+ int[] count, int countAmt) { -+ BlockPos pos1 = pos.relative(dir, c); -+ giveShapeUpdate(world, mainState, pos1.west(), pos, Direction.WEST); -+ giveShapeUpdate(world, mainState, pos1.east(), pos, Direction.EAST); -+ giveShapeUpdate(world, mainState, pos1.below(), pos, Direction.DOWN); -+ giveShapeUpdate(world, mainState, pos1.above(), pos, Direction.UP); -+ shapeUpdateEnd(self, world, pos, mainState, countAmt, dir, c, pos1); -+ if (c == 0 && count[1] == 0) -+ giveShapeUpdate(world, mainState, pos1.relative(dir.getOpposite()), pos, dir.getOpposite()); -+ } -+ -+ private static void updateRails(PoweredRailBlock self, boolean eastWest, Level world, -+ BlockPos pos, BlockState mainState, int[] count) { -+ if (eastWest) { -+ for (int i = 0; i < EAST_WEST_DIR.length; ++i) { -+ int countAmt = count[i]; -+ if (i == 1 && countAmt == 0) continue; -+ Direction dir = EAST_WEST_DIR[i]; -+ Block block = mainState.getBlock(); -+ for (int c = countAmt; c >= i; c--) { -+ BlockPos p = pos.relative(dir, c); -+ if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()), block, pos); -+ neighborUpdateEnd(self, world, pos, countAmt, dir, block, c, p); -+ world.neighborChanged(p.below(), block, pos); -+ world.neighborChanged(p.above(), block, pos); -+ world.neighborChanged(p.north(), block, pos); -+ world.neighborChanged(p.south(), block, pos); -+ BlockPos pos2 = pos.relative(dir, c).below(); -+ world.neighborChanged(pos2.below(), block, pos); -+ world.neighborChanged(pos2.north(), block, pos); -+ world.neighborChanged(pos2.south(), block, pos); -+ if (c == countAmt) world.neighborChanged(pos.relative(dir, c + 1).below(), block, pos); -+ if (c == 0 && count[1] == 0) -+ world.neighborChanged(p.relative(dir.getOpposite()).below(), block, pos); -+ } -+ for (int c = countAmt; c >= i; c--) -+ updateRailsSectionEastWestShape(self, world, pos, c, mainState, dir, count, countAmt); -+ } -+ } else { -+ for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) { -+ int countAmt = count[i]; -+ if (i == 1 && countAmt == 0) continue; -+ Direction dir = NORTH_SOUTH_DIR[i]; -+ Block block = mainState.getBlock(); -+ for (int c = countAmt; c >= i; c--) { -+ BlockPos p = pos.relative(dir, c); -+ world.neighborChanged(p.west(), block, pos); -+ world.neighborChanged(p.east(), block, pos); -+ world.neighborChanged(p.below(), block, pos); -+ world.neighborChanged(p.above(), block, pos); -+ neighborUpdateEnd(self, world, pos, countAmt, dir, block, c, p); -+ if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()), block, pos); -+ BlockPos pos2 = pos.relative(dir, c).below(); -+ world.neighborChanged(pos2.west(), block, pos); -+ world.neighborChanged(pos2.east(), block, pos); -+ world.neighborChanged(pos2.below(), block, pos); -+ if (c == countAmt) world.neighborChanged(pos.relative(dir, c + 1).below(), block, pos); -+ if (c == 0 && count[1] == 0) -+ world.neighborChanged(p.relative(dir.getOpposite()).below(), block, pos); -+ } -+ for (int c = countAmt; c >= i; c--) -+ updateRailsSectionNorthSouthShape(self, world, pos, c, mainState, dir, count, countAmt); -+ } -+ } -+ } -+} -\ No newline at end of file diff --git a/leaf-server/minecraft-patches/features/0007-Purpur-Server-Minecraft-Changes.patch b/leaf-server/minecraft-patches/features/0007-Purpur-Server-Minecraft-Changes.patch index c4764b6e..86de64ae 100644 --- a/leaf-server/minecraft-patches/features/0007-Purpur-Server-Minecraft-Changes.patch +++ b/leaf-server/minecraft-patches/features/0007-Purpur-Server-Minecraft-Changes.patch @@ -15798,13 +15798,13 @@ index 9c0ded7ae7e3a520704033a866f80743ae85d772..4f3646961beb877520e257e11224c304 } // CraftBukkit end diff --git a/net/minecraft/world/level/block/PoweredRailBlock.java b/net/minecraft/world/level/block/PoweredRailBlock.java -index f33b53257a231dccf16740f1e78a3cdfa6e70726..4b3d00b085ea5d345b418815c9869f5f8c9fe558 100644 +index be3b5531cab74df8770ffa04ec4a4bae28cc57e6..e6674c37e09fe0ad2e971bd957666929492ba57e 100644 --- a/net/minecraft/world/level/block/PoweredRailBlock.java +++ b/net/minecraft/world/level/block/PoweredRailBlock.java @@ -34,7 +34,7 @@ public class PoweredRailBlock extends BaseRailBlock { } - protected boolean findPoweredRailSignal(Level level, BlockPos pos, BlockState state, boolean searchForward, int recursionCount) { + public boolean findPoweredRailSignal(Level level, BlockPos pos, BlockState state, boolean searchForward, int recursionCount) { - if (recursionCount >= 8) { + if (recursionCount >= level.purpurConfig.railActivationRange) { // Purpur - Config for powered rail activation distance return false; diff --git a/leaf-server/minecraft-patches/features/0183-Rail-Optimization-optimized-PoweredRailBlock-logic.patch b/leaf-server/minecraft-patches/features/0183-Rail-Optimization-optimized-PoweredRailBlock-logic.patch new file mode 100644 index 00000000..086df508 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0183-Rail-Optimization-optimized-PoweredRailBlock-logic.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Sat, 17 Feb 2024 17:57:08 -0500 +Subject: [PATCH] Rail Optimization: optimized PoweredRailBlock logic + +Original project: https://github.com/FxMorin/RailOptimization + +Full Rewrite of the powered rail iteration logic +that makes powered/activator rails turning on/off up to 4x faster. +This rewrite brings a massive performance boost while keeping the vanilla order. This is achieved by running all the +powered rail logic from a single rail instead of each block iterating separately. Which was not only very +expensive but also completely unnecessary and with a lot of massive overhead + +diff --git a/net/minecraft/world/level/block/PoweredRailBlock.java b/net/minecraft/world/level/block/PoweredRailBlock.java +index e6674c37e09fe0ad2e971bd957666929492ba57e..c93a7d166ac97bac981d906c6894606237107fe8 100644 +--- a/net/minecraft/world/level/block/PoweredRailBlock.java ++++ b/net/minecraft/world/level/block/PoweredRailBlock.java +@@ -128,6 +128,12 @@ public class PoweredRailBlock extends BaseRailBlock { + + @Override + protected void updateState(BlockState state, Level level, BlockPos pos, Block block) { ++ // Leaf start - Rail Optimization ++ if (org.dreeam.leaf.config.modules.opt.OptimizedPoweredRails.enabled) { ++ org.dreeam.leaf.optimize.OptimizedPoweredRails.updateState(this, state, level, pos); ++ return; ++ } ++ // Leaf end - Rail Optimization + boolean poweredValue = state.getValue(POWERED); + boolean flag = level.hasNeighborSignal(pos) + || this.findPoweredRailSignal(level, pos, state, true, 0) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizedPoweredRails.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizedPoweredRails.java new file mode 100644 index 00000000..b71f3702 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizedPoweredRails.java @@ -0,0 +1,18 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class OptimizedPoweredRails extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName() + ".optimized-powered-rails"; + } + + public static boolean enabled = false; + + @Override + public void onLoaded() { + enabled = config().getBoolean(getBasePath(), enabled); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/optimize/OptimizedPoweredRails.java b/leaf-server/src/main/java/org/dreeam/leaf/optimize/OptimizedPoweredRails.java new file mode 100644 index 00000000..87b9a745 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/optimize/OptimizedPoweredRails.java @@ -0,0 +1,333 @@ +package org.dreeam.leaf.optimize; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.PoweredRailBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.RailShape; + +import java.util.HashMap; + +import static net.minecraft.world.level.block.Block.*; +import static net.minecraft.world.level.block.PoweredRailBlock.POWERED; +import static net.minecraft.world.level.block.PoweredRailBlock.SHAPE; + +public class OptimizedPoweredRails { + + private static final Direction[] EAST_WEST_DIR = new Direction[]{Direction.WEST, Direction.EAST}; + private static final Direction[] NORTH_SOUTH_DIR = new Direction[]{Direction.SOUTH, Direction.NORTH}; + + private static final int UPDATE_FORCE_PLACE = UPDATE_MOVE_BY_PISTON | UPDATE_KNOWN_SHAPE | UPDATE_CLIENTS; + + private static int RAIL_POWER_LIMIT = 8; + + private static void giveShapeUpdate(Level level, BlockState state, BlockPos pos, BlockPos fromPos, Direction direction) { + BlockState oldState = level.getBlockState(pos); + Block.updateOrDestroy( + oldState, + oldState.updateShape(level, level, pos, direction.getOpposite(), fromPos, state, level.random), + level, + pos, + UPDATE_CLIENTS & -34, + 0 + ); + } + + public static int getRailPowerLimit() { + return RAIL_POWER_LIMIT; + } + + public static void setRailPowerLimit(int powerLimit) { + RAIL_POWER_LIMIT = powerLimit; + } + + public static void updateState(PoweredRailBlock self, BlockState state, Level level, BlockPos pos) { + boolean shouldBePowered = level.hasNeighborSignal(pos) || + self.findPoweredRailSignal(level, pos, state, true, 0) || + self.findPoweredRailSignal(level, pos, state, false, 0); + if (shouldBePowered != state.getValue(POWERED)) { + RailShape railShape = state.getValue(SHAPE); + if (railShape.isSlope()) { + level.setBlock(pos, state.setValue(POWERED, shouldBePowered), 3); + level.updateNeighborsAtExceptFromFacing(pos.below(), self, Direction.UP, null); + level.updateNeighborsAtExceptFromFacing(pos.above(), self, Direction.DOWN, null); // isSlope + } else if (shouldBePowered) { + powerLane(self, level, pos, state, railShape); + } else { + dePowerLane(self, level, pos, state, railShape); + } + } + } + + private static boolean findPoweredRailSignalFaster(PoweredRailBlock self, Level world, BlockPos pos, + boolean bl, int distance, RailShape shape, + HashMap checkedPos) { + BlockState blockState = world.getBlockState(pos); + boolean speedCheck = checkedPos.containsKey(pos) && checkedPos.get(pos); + if (speedCheck) { + return world.hasNeighborSignal(pos) || + findPoweredRailSignalFaster(self, world, pos, blockState, bl, distance + 1, checkedPos); + } else { + if (blockState.is(self)) { + RailShape railShape = blockState.getValue(SHAPE); + if (shape == RailShape.EAST_WEST && ( + railShape == RailShape.NORTH_SOUTH || + railShape == RailShape.ASCENDING_NORTH || + railShape == RailShape.ASCENDING_SOUTH + ) || shape == RailShape.NORTH_SOUTH && ( + railShape == RailShape.EAST_WEST || + railShape == RailShape.ASCENDING_EAST || + railShape == RailShape.ASCENDING_WEST + )) { + return false; + } else if (blockState.getValue(POWERED)) { + return world.hasNeighborSignal(pos) || + findPoweredRailSignalFaster(self, world, pos, blockState, bl, distance + 1, checkedPos); + } else { + return false; + } + } + return false; + } + } + + private static boolean findPoweredRailSignalFaster(PoweredRailBlock self, Level level, + BlockPos pos, BlockState state, boolean bl, int distance, + HashMap checkedPos) { + if (distance >= RAIL_POWER_LIMIT - 1) return false; + int i = pos.getX(); + int j = pos.getY(); + int k = pos.getZ(); + boolean bl2 = true; + RailShape railShape = state.getValue(SHAPE); + switch (railShape.ordinal()) { + case 0 -> { + if (bl) ++k; + else --k; + } + case 1 -> { + if (bl) --i; + else ++i; + } + case 2 -> { + if (bl) { + --i; + } else { + ++i; + ++j; + bl2 = false; + } + railShape = RailShape.EAST_WEST; + } + case 3 -> { + if (bl) { + --i; + ++j; + bl2 = false; + } else { + ++i; + } + railShape = RailShape.EAST_WEST; + } + case 4 -> { + if (bl) { + ++k; + } else { + --k; + ++j; + bl2 = false; + } + railShape = RailShape.NORTH_SOUTH; + } + case 5 -> { + if (bl) { + ++k; + ++j; + bl2 = false; + } else { + --k; + } + railShape = RailShape.NORTH_SOUTH; + } + } + return findPoweredRailSignalFaster( + self, level, new BlockPos(i, j, k), + bl, distance, railShape, checkedPos + ) || + (bl2 && findPoweredRailSignalFaster( + self, level, new BlockPos(i, j - 1, k), + bl, distance, railShape, checkedPos + )); + } + + private static void powerLane(PoweredRailBlock self, Level world, BlockPos pos, + BlockState mainState, RailShape railShape) { + world.setBlock(pos, mainState.setValue(POWERED, true), UPDATE_FORCE_PLACE); + HashMap checkedPos = new HashMap<>(); + checkedPos.put(pos, true); + int[] count = new int[2]; + if (railShape == RailShape.NORTH_SOUTH) { // Order: +z, -z + for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) { + setRailPositionsPower(self, world, pos, checkedPos, count, i, NORTH_SOUTH_DIR[i]); + } + updateRails(self, false, world, pos, mainState, count); + } else if (railShape == RailShape.EAST_WEST) { // Order: -x, +x + for (int i = 0; i < EAST_WEST_DIR.length; ++i) { + setRailPositionsPower(self, world, pos, checkedPos, count, i, EAST_WEST_DIR[i]); + } + updateRails(self, true, world, pos, mainState, count); + } + } + + private static void dePowerLane(PoweredRailBlock self, Level world, BlockPos pos, + BlockState mainState, RailShape railShape) { + world.setBlock(pos, mainState.setValue(POWERED, false), UPDATE_FORCE_PLACE); + int[] count = new int[2]; + if (railShape == RailShape.NORTH_SOUTH) { // Order: +z, -z + for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) { + setRailPositionsDePower(self, world, pos, count, i, NORTH_SOUTH_DIR[i]); + } + updateRails(self, false, world, pos, mainState, count); + } else if (railShape == RailShape.EAST_WEST) { // Order: -x, +x + for (int i = 0; i < EAST_WEST_DIR.length; ++i) { + setRailPositionsDePower(self, world, pos, count, i, EAST_WEST_DIR[i]); + } + updateRails(self, true, world, pos, mainState, count); + } + } + + private static void setRailPositionsPower(PoweredRailBlock self, Level world, BlockPos pos, + HashMap checkedPos, int[] count, int i, Direction dir) { + for (int z = 1; z < RAIL_POWER_LIMIT; z++) { + BlockPos newPos = pos.relative(dir, z); + BlockState state = world.getBlockState(newPos); + if (checkedPos.containsKey(newPos)) { + if (!checkedPos.get(newPos)) break; + count[i]++; + } else if (!state.is(self) || state.getValue(POWERED) || !( + world.hasNeighborSignal(newPos) || + findPoweredRailSignalFaster(self, world, newPos, state, true, 0, checkedPos) || + findPoweredRailSignalFaster(self, world, newPos, state, false, 0, checkedPos) + )) { + checkedPos.put(newPos, false); + break; + } else { + checkedPos.put(newPos, true); + world.setBlock(newPos, state.setValue(POWERED, true), UPDATE_FORCE_PLACE); + count[i]++; + } + } + } + + private static void setRailPositionsDePower(PoweredRailBlock self, Level world, BlockPos pos, + int[] count, int i, Direction dir) { + for (int z = 1; z < RAIL_POWER_LIMIT; z++) { + BlockPos newPos = pos.relative(dir, z); + BlockState state = world.getBlockState(newPos); + if (!state.is(self) || !state.getValue(POWERED) || world.hasNeighborSignal(newPos) || + self.findPoweredRailSignal(world, newPos, state, true, 0) || + self.findPoweredRailSignal(world, newPos, state, false, 0)) break; + world.setBlock(newPos, state.setValue(POWERED, false), UPDATE_FORCE_PLACE); + count[i]++; + } + } + + private static void shapeUpdateEnd(PoweredRailBlock self, Level world, BlockPos pos, BlockState mainState, + int endPos, Direction direction, int currentPos, BlockPos blockPos) { + if (currentPos == endPos) { + BlockPos newPos = pos.relative(direction, currentPos + 1); + giveShapeUpdate(world, mainState, newPos, pos, direction); + BlockState state = world.getBlockState(blockPos); + if (state.is(self) && state.getValue(SHAPE).isSlope()) giveShapeUpdate(world, mainState, newPos.above(), pos, direction); + } + } + + private static void neighborUpdateEnd(PoweredRailBlock self, Level world, BlockPos pos, int endPos, + Direction direction, Block block, int currentPos, BlockPos blockPos) { + if (currentPos == endPos) { + BlockPos newPos = pos.relative(direction, currentPos + 1); + world.neighborChanged(newPos, block, null); + BlockState state = world.getBlockState(blockPos); + if (state.is(self) && state.getValue(SHAPE).isSlope()) world.neighborChanged(newPos.above(), block, null); + } + } + + private static void updateRailsSectionEastWestShape(PoweredRailBlock self, Level world, BlockPos pos, + int c, BlockState mainState, Direction dir, + int[] count, int countAmt) { + BlockPos pos1 = pos.relative(dir, c); + if (c == 0 && count[1] == 0) giveShapeUpdate(world, mainState, pos1.relative(dir.getOpposite()), pos, dir.getOpposite()); + shapeUpdateEnd(self, world, pos, mainState, countAmt, dir, c, pos1); + giveShapeUpdate(world, mainState, pos1.below(), pos, Direction.DOWN); + giveShapeUpdate(world, mainState, pos1.above(), pos, Direction.UP); + giveShapeUpdate(world, mainState, pos1.north(), pos, Direction.NORTH); + giveShapeUpdate(world, mainState, pos1.south(), pos, Direction.SOUTH); + } + + private static void updateRailsSectionNorthSouthShape(PoweredRailBlock self, Level world, BlockPos pos, + int c, BlockState mainState, Direction dir, + int[] count, int countAmt) { + BlockPos pos1 = pos.relative(dir, c); + giveShapeUpdate(world, mainState, pos1.west(), pos, Direction.WEST); + giveShapeUpdate(world, mainState, pos1.east(), pos, Direction.EAST); + giveShapeUpdate(world, mainState, pos1.below(), pos, Direction.DOWN); + giveShapeUpdate(world, mainState, pos1.above(), pos, Direction.UP); + shapeUpdateEnd(self, world, pos, mainState, countAmt, dir, c, pos1); + if (c == 0 && count[1] == 0) giveShapeUpdate(world, mainState, pos1.relative(dir.getOpposite()), pos, dir.getOpposite()); + } + + private static void updateRails(PoweredRailBlock self, boolean eastWest, Level world, + BlockPos pos, BlockState mainState, int[] count) { + if (eastWest) { + for (int i = 0; i < EAST_WEST_DIR.length; ++i) { + int countAmt = count[i]; + if (i == 1 && countAmt == 0) continue; + Direction dir = EAST_WEST_DIR[i]; + Block block = mainState.getBlock(); + for (int c = countAmt; c >= i; c--) { + BlockPos p = pos.relative(dir, c); + if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()), block, null); + neighborUpdateEnd(self, world, pos, countAmt, dir, block, c, p); + world.neighborChanged(p.below(), block, null); + world.neighborChanged(p.above(), block, null); + world.neighborChanged(p.north(), block, null); + world.neighborChanged(p.south(), block, null); + BlockPos pos2 = pos.relative(dir, c).below(); + world.neighborChanged(pos2.below(), block, null); + world.neighborChanged(pos2.north(), block, null); + world.neighborChanged(pos2.south(), block, null); + if (c == countAmt) world.neighborChanged(pos.relative(dir, c + 1).below(), block, null); + if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()).below(), block, null); + } + for (int c = countAmt; c >= i; c--) + updateRailsSectionEastWestShape(self, world, pos, c, mainState, dir, count, countAmt); + } + } else { + for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) { + int countAmt = count[i]; + if (i == 1 && countAmt == 0) continue; + Direction dir = NORTH_SOUTH_DIR[i]; + Block block = mainState.getBlock(); + for (int c = countAmt; c >= i; c--) { + BlockPos p = pos.relative(dir, c); + world.neighborChanged(p.west(), block, null); + world.neighborChanged(p.east(), block, null); + world.neighborChanged(p.below(), block, null); + world.neighborChanged(p.above(), block, null); + neighborUpdateEnd(self, world, pos, countAmt, dir, block, c, p); + if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()), block, null); + BlockPos pos2 = pos.relative(dir, c).below(); + world.neighborChanged(pos2.west(), block, null); + world.neighborChanged(pos2.east(), block, null); + world.neighborChanged(pos2.below(), block, null); + if (c == countAmt) world.neighborChanged(pos.relative(dir, c + 1).below(), block, null); + if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()).below(), block, null); + } + for (int c = countAmt; c >= i; c--) + updateRailsSectionNorthSouthShape(self, world, pos, c, mainState, dir, count, countAmt); + } + } + } +} From 59d350d50fc006e2f9e47b177118430a0f864cad Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Mon, 26 May 2025 15:14:38 +0800 Subject: [PATCH 06/25] Move packages --- build.gradle.kts | 2 +- .../features/0071-Reduce-worldgen-allocations.patch | 6 +++--- ...Rail-Optimization-optimized-PoweredRailBlock-logic.patch | 4 ++-- .../leaf/{util => world}/biome/PositionalBiomeGetter.java | 2 +- .../{optimize => world/block}/OptimizedPoweredRails.java | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) rename leaf-server/src/main/java/org/dreeam/leaf/{util => world}/biome/PositionalBiomeGetter.java (96%) rename leaf-server/src/main/java/org/dreeam/leaf/{optimize => world/block}/OptimizedPoweredRails.java (99%) diff --git a/build.gradle.kts b/build.gradle.kts index b49ba44d..e9187fca 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,7 +35,7 @@ subprojects { options.release = 21 options.isFork = true options.compilerArgs.addAll(listOf("-Xlint:-deprecation", "-Xlint:-removal")) - options.forkOptions.memoryMaximumSize = "6g" // Prevent OOM during building + options.forkOptions.memoryMaximumSize = "2g" // Prevent OOM during building } tasks.withType { options.encoding = Charsets.UTF_8.name() diff --git a/leaf-server/minecraft-patches/features/0071-Reduce-worldgen-allocations.patch b/leaf-server/minecraft-patches/features/0071-Reduce-worldgen-allocations.patch index d741c54a..cee918c6 100644 --- a/leaf-server/minecraft-patches/features/0071-Reduce-worldgen-allocations.patch +++ b/leaf-server/minecraft-patches/features/0071-Reduce-worldgen-allocations.patch @@ -34,7 +34,7 @@ index f861f9e087182470a3bbb22678dbdacb8a73e943..a3d0d17178eedfaef83e2e0df6b1c2d7 private DensityFunction wrapNew(DensityFunction densityFunction) { diff --git a/net/minecraft/world/level/levelgen/SurfaceRules.java b/net/minecraft/world/level/levelgen/SurfaceRules.java -index 0948c8db90605a15a043b5c5bc74edecd7f9db1b..009e8a270c25614d03413d8b8b1f39c2da8ba12f 100644 +index 0948c8db90605a15a043b5c5bc74edecd7f9db1b..6cba88415a4715527e163e54662db9b3ab37c747 100644 --- a/net/minecraft/world/level/levelgen/SurfaceRules.java +++ b/net/minecraft/world/level/levelgen/SurfaceRules.java @@ -313,8 +313,15 @@ public class SurfaceRules { @@ -48,9 +48,9 @@ index 0948c8db90605a15a043b5c5bc74edecd7f9db1b..009e8a270c25614d03413d8b8b1f39c2 + ++this.lastUpdateY; + Supplier> getter = this.biome; + if (getter == null) { -+ this.biome = getter = new org.dreeam.leaf.util.biome.PositionalBiomeGetter(this.biomeGetter, this.pos); ++ this.biome = getter = new org.dreeam.leaf.world.biome.PositionalBiomeGetter(this.biomeGetter, this.pos); + } -+ ((org.dreeam.leaf.util.biome.PositionalBiomeGetter) getter).update(blockX, blockY, blockZ); ++ ((org.dreeam.leaf.world.biome.PositionalBiomeGetter) getter).update(blockX, blockY, blockZ); + // Leaf end - Reduce worldgen allocations this.blockY = blockY; this.waterHeight = waterHeight; diff --git a/leaf-server/minecraft-patches/features/0183-Rail-Optimization-optimized-PoweredRailBlock-logic.patch b/leaf-server/minecraft-patches/features/0183-Rail-Optimization-optimized-PoweredRailBlock-logic.patch index 086df508..824cfd93 100644 --- a/leaf-server/minecraft-patches/features/0183-Rail-Optimization-optimized-PoweredRailBlock-logic.patch +++ b/leaf-server/minecraft-patches/features/0183-Rail-Optimization-optimized-PoweredRailBlock-logic.patch @@ -12,7 +12,7 @@ powered rail logic from a single rail instead of each block iterating separately expensive but also completely unnecessary and with a lot of massive overhead diff --git a/net/minecraft/world/level/block/PoweredRailBlock.java b/net/minecraft/world/level/block/PoweredRailBlock.java -index e6674c37e09fe0ad2e971bd957666929492ba57e..c93a7d166ac97bac981d906c6894606237107fe8 100644 +index e6674c37e09fe0ad2e971bd957666929492ba57e..653551f87f7c6dc20189ead09dcc81661afca2d8 100644 --- a/net/minecraft/world/level/block/PoweredRailBlock.java +++ b/net/minecraft/world/level/block/PoweredRailBlock.java @@ -128,6 +128,12 @@ public class PoweredRailBlock extends BaseRailBlock { @@ -21,7 +21,7 @@ index e6674c37e09fe0ad2e971bd957666929492ba57e..c93a7d166ac97bac981d906c68946062 protected void updateState(BlockState state, Level level, BlockPos pos, Block block) { + // Leaf start - Rail Optimization + if (org.dreeam.leaf.config.modules.opt.OptimizedPoweredRails.enabled) { -+ org.dreeam.leaf.optimize.OptimizedPoweredRails.updateState(this, state, level, pos); ++ org.dreeam.leaf.world.block.OptimizedPoweredRails.updateState(this, state, level, pos); + return; + } + // Leaf end - Rail Optimization diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/biome/PositionalBiomeGetter.java b/leaf-server/src/main/java/org/dreeam/leaf/world/biome/PositionalBiomeGetter.java similarity index 96% rename from leaf-server/src/main/java/org/dreeam/leaf/util/biome/PositionalBiomeGetter.java rename to leaf-server/src/main/java/org/dreeam/leaf/world/biome/PositionalBiomeGetter.java index 042dde39..a47e7348 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/util/biome/PositionalBiomeGetter.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/world/biome/PositionalBiomeGetter.java @@ -1,4 +1,4 @@ -package org.dreeam.leaf.util.biome; +package org.dreeam.leaf.world.biome; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; diff --git a/leaf-server/src/main/java/org/dreeam/leaf/optimize/OptimizedPoweredRails.java b/leaf-server/src/main/java/org/dreeam/leaf/world/block/OptimizedPoweredRails.java similarity index 99% rename from leaf-server/src/main/java/org/dreeam/leaf/optimize/OptimizedPoweredRails.java rename to leaf-server/src/main/java/org/dreeam/leaf/world/block/OptimizedPoweredRails.java index 87b9a745..f888ff94 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/optimize/OptimizedPoweredRails.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/world/block/OptimizedPoweredRails.java @@ -1,4 +1,4 @@ -package org.dreeam.leaf.optimize; +package org.dreeam.leaf.world.block; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; From c8f0a78d7514232ab89f0797ad77b4390dfc25b4 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Mon, 26 May 2025 18:41:08 +0900 Subject: [PATCH 07/25] low freq poll --- .../features/0153-Async-target-finding.patch | 10 +++++-- .../leaf/async/ai/AsyncGoalExecutor.java | 27 +++++++++---------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch index fd386af4..8bd2753c 100644 --- a/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch @@ -669,7 +669,7 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..0a41797fd7beddce0b93d42bac6e0270 } else { this.parent = animal; diff --git a/net/minecraft/world/entity/ai/goal/GoalSelector.java b/net/minecraft/world/entity/ai/goal/GoalSelector.java -index e82e32407cec6109b9c3b0106295217f4a3f4aa2..3c24382a3cced8dcea103ccc87cb506310de8461 100644 +index e82e32407cec6109b9c3b0106295217f4a3f4aa2..f522125d19e31ec3fd3d38d1fdb307ba25e300a8 100644 --- a/net/minecraft/world/entity/ai/goal/GoalSelector.java +++ b/net/minecraft/world/entity/ai/goal/GoalSelector.java @@ -26,13 +26,23 @@ public class GoalSelector { @@ -850,7 +850,7 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..3c24382a3cced8dcea103ccc87cb5063 for (WrappedGoal wrappedGoal : this.availableGoals) { if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.goalTypes) || !wrappedGoal.canContinueToUse())) { // Paper - Perf: optimize goal types by removing streams wrappedGoal.stop(); -@@ -116,6 +251,18 @@ public class GoalSelector { +@@ -116,6 +251,24 @@ public class GoalSelector { } public void tickRunningGoals(boolean tickAllRunning) { @@ -862,6 +862,12 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..3c24382a3cced8dcea103ccc87cb5063 + availableGoalsDirty = false; + } + ctxState = tickAllRunning ? 2 : 3; ++ } else { ++ for (WrappedGoal wrappedGoal : this.ctxGoals) { ++ if (wrappedGoal.isRunning() && (tickAllRunning || wrappedGoal.requiresUpdateEveryTick())) { ++ wrappedGoal.tick(); ++ } ++ } + } + return; + } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java index 084ed5df..780e2678 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java @@ -18,7 +18,6 @@ public class AsyncGoalExecutor { protected final SpscIntQueue queue; protected final SpscIntQueue wake; protected final IntArrayList submit; - private final AsyncGoalThread thread; private final ServerLevel world; private long midTickCount = 0L; @@ -27,7 +26,6 @@ public class AsyncGoalExecutor { this.queue = new SpscIntQueue(AsyncTargetFinding.queueSize); this.wake = new SpscIntQueue(AsyncTargetFinding.queueSize); this.submit = new IntArrayList(); - this.thread = thread; } boolean wake(int id) { @@ -46,7 +44,18 @@ public class AsyncGoalExecutor { public final void tick() { batchSubmit(); - LockSupport.unpark(thread); + while (true) { + OptionalInt result = this.wake.recv(); + if (result.isEmpty()) { + break; + } + int id = result.getAsInt(); + if (poll(id) && !this.queue.send(id)) { + do { + wake(id); + } while (poll(id)); + } + } } private void batchSubmit() { @@ -67,18 +76,6 @@ public class AsyncGoalExecutor { } public final void midTick() { - while (true) { - OptionalInt result = this.wake.recv(); - if (result.isEmpty()) { - break; - } - int id = result.getAsInt(); - if (poll(id) && !this.queue.send(id)) { - do { - wake(id); - } while (poll(id)); - } - } if (AsyncTargetFinding.threshold <= 0L || (midTickCount % AsyncTargetFinding.threshold) == 0L) { batchSubmit(); } From a2e5d6d9009c7af1597503a60e35eefc8fdbac97 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Mon, 26 May 2025 18:41:53 +0900 Subject: [PATCH 08/25] fix check LlamaFollowCaravanGoal --- .../features/0153-Async-target-finding.patch | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch index 8bd2753c..5b8bc147 100644 --- a/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch @@ -876,7 +876,7 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..f522125d19e31ec3fd3d38d1fdb307ba if (wrappedGoal.isRunning() && (tickAllRunning || wrappedGoal.requiresUpdateEveryTick())) { wrappedGoal.tick(); diff --git a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java -index be59d0c27a83b329ec3f97c029cfb9c114e22472..86f041ff21cf44a0cded5744055654a0bff40c42 100644 +index be59d0c27a83b329ec3f97c029cfb9c114e22472..5c70f33d9633326cdc59fd0812f49aa38b3d6e14 100644 --- a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java +++ b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java @@ -20,20 +20,83 @@ public class LlamaFollowCaravanGoal extends Goal { @@ -941,7 +941,7 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..86f041ff21cf44a0cded5744055654a0 + // Leaf start - Async Target Finding + Llama llama = poll(); + double d = Double.MAX_VALUE; -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + if (llama == null) { + findTargetAsync(); + return false; From ad87353c4aebc240a5b517532fc7c7be712e7343 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Sun, 25 May 2025 20:39:13 +0900 Subject: [PATCH 09/25] improve plugin compatibility --- .../features/0153-Async-target-finding.patch | 88 ++++++++++++------- 1 file changed, 54 insertions(+), 34 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch index 5b8bc147..684ffe8b 100644 --- a/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch @@ -1086,10 +1086,10 @@ index 6463c3c9b08d6058f2843c225b08a40fc30a960b..98c2b4a298ada4b02afa55f991791d86 @Override public boolean canContinueToUse() { diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..9b8453ce2bc2cafca7c670d79b40434e7c93afca 100644 +index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..52e353ff7865cc187d83ada4e93dc955ead2a578 100644 --- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -@@ -41,8 +41,60 @@ public abstract class MoveToBlockGoal extends Goal { +@@ -41,14 +41,67 @@ public abstract class MoveToBlockGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.JUMP)); } @@ -1137,6 +1137,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..9b8453ce2bc2cafca7c670d79b40434e + Strider, + TurtleToWater, + TurtleLay, ++ Unknown, + } + // Leaf end - Async search block + @@ -1150,20 +1151,32 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..9b8453ce2bc2cafca7c670d79b40434e if (this.nextStartTick > 0) { this.nextStartTick--; return false; -@@ -109,6 +161,12 @@ public abstract class MoveToBlockGoal extends Goal { + } else { + this.nextStartTick = this.nextStartTick(this.mob); +- return this.findNearestBlock(); ++ return this.findNearestBlockAsync(); // Leaf - Async search block + } } - protected boolean findNearestBlock() { -+ // Leaf start - Async search block -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchBlock) { +@@ -108,6 +161,17 @@ public abstract class MoveToBlockGoal extends Goal { + return this.reachedTarget; + } + ++ // Leaf start - Async search block ++ protected boolean findNearestBlockAsync() { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchBlock ++ && this.typeToCheck() != TypeToCheck.Unknown) { + getBlockAsync(); + return false; + } -+ // Leaf end - Async search block ++ return findNearestBlock(); ++ } ++ // Leaf end - Async search block ++ + protected boolean findNearestBlock() { int i = this.searchRange; int i1 = this.verticalSearchRange; - BlockPos blockPos = this.mob.blockPosition(); -@@ -133,5 +191,105 @@ public abstract class MoveToBlockGoal extends Goal { +@@ -133,5 +197,108 @@ public abstract class MoveToBlockGoal extends Goal { return false; } @@ -1206,7 +1219,9 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..9b8453ce2bc2cafca7c670d79b40434e protected abstract boolean isValidTarget(LevelReader level, BlockPos pos); + + // Leaf start - Async search block -+ protected abstract TypeToCheck typeToCheck(); ++ protected TypeToCheck typeToCheck() { ++ return TypeToCheck.Unknown; ++ } + + private static boolean isValidTargetAsync( + TypeToCheck type, @@ -1264,6 +1279,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..9b8453ce2bc2cafca7c670d79b40434e + case TurtleLay -> { + return level.isEmptyBlock(pos.above()) && net.minecraft.world.level.block.TurtleEggBlock.isSand(level, pos); + } ++ case Unknown -> throw new IllegalStateException(); + case null -> throw new IllegalStateException(); + } + // Leaf end - Async search block @@ -1333,10 +1349,10 @@ index 3c274d917bca9de87abfb842f5f332e112a7a2d7..2491b84641443ecfb8afc3b179e1cf80 public boolean canContinueToUse() { return this.tick > 0; diff --git a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java -index c67a88c9c77ece7c85ffb169ac96da4f28291228..14d9b492ba431d534e0c6a567d0b7700b4c8a02d 100644 +index c67a88c9c77ece7c85ffb169ac96da4f28291228..93bf51c9299354be2a581618bd31d2c4cc7c1d30 100644 --- a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java +++ b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java -@@ -37,7 +37,14 @@ public class RemoveBlockGoal extends MoveToBlockGoal { +@@ -37,10 +37,17 @@ public class RemoveBlockGoal extends MoveToBlockGoal { public boolean canUse() { if (!getServerLevel(this.removerMob).purpurConfig.zombieBypassMobGriefing == !getServerLevel(this.removerMob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected return false; @@ -1351,7 +1367,11 @@ index c67a88c9c77ece7c85ffb169ac96da4f28291228..14d9b492ba431d534e0c6a567d0b7700 + if (this.nextStartTick > 0) { this.nextStartTick--; return false; - } else if (this.findNearestBlock()) { +- } else if (this.findNearestBlock()) { ++ } else if (this.findNearestBlockAsync()) { // Leaf - async search block + this.nextStartTick = reducedTickDelay(20); + return true; + } else { @@ -151,8 +158,15 @@ public class RemoveBlockGoal extends MoveToBlockGoal { protected boolean isValidTarget(LevelReader level, BlockPos pos) { ChunkAccess chunk = level.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4); // Paper - Prevent AI rules from loading chunks @@ -1604,10 +1624,10 @@ index 25fe78116ce01eeefe5c958423734195d27302eb..e306c1cfc44878ea130d8046b31cf617 mob.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY, true); // CraftBukkit - reason } diff --git a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -index 85eae0a14f7a417dfd8c911079d05354a98e5834..f59d5c9be0eb10f5b5192442e1850900d71a31e9 100644 +index 85eae0a14f7a417dfd8c911079d05354a98e5834..745ef9ea16171d8dad3082878f8883031bd79a84 100644 --- a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -@@ -41,8 +41,43 @@ public class NearestAttackableTargetGoal extends TargetG +@@ -41,12 +41,52 @@ public class NearestAttackableTargetGoal extends TargetG this.targetConditions = TargetingConditions.forCombat().range(this.getFollowDistance()).selector(selector); } @@ -1620,7 +1640,12 @@ index 85eae0a14f7a417dfd8c911079d05354a98e5834..f59d5c9be0eb10f5b5192442e1850900 + return true; + } + -+ private void findTargetAsync() { ++ protected void findTargetAsync() { ++ if (!org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ this.findTarget(); ++ return; ++ } ++ + final Mob mob = this.mob; + final var ctx = mob.getGoalCtx(); + if (!ctx.state) return; @@ -1651,27 +1676,16 @@ index 85eae0a14f7a417dfd8c911079d05354a98e5834..f59d5c9be0eb10f5b5192442e1850900 if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) { return false; } else { -@@ -57,6 +92,15 @@ public class NearestAttackableTargetGoal extends TargetG - - protected void findTarget() { - ServerLevel serverLevel = getServerLevel(this.mob); -+ -+ // Leaf start - Async Target Finding -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { -+ this.findTargetAsync(); -+ this.target = null; -+ return; -+ } -+ // Leaf end - Async Target Finding -+ - if (this.targetType != Player.class && this.targetType != ServerPlayer.class) { - this.target = serverLevel.getNearestEntity( - this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true), +- this.findTarget(); ++ this.findTargetAsync(); // Leaf - Async target finding + return this.target != null; + } + } diff --git a/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java -index 4604a603c4ddd0a9242e859aaa5a511c2d4c4f84..7274dfb52ca4a08cdebcd04294cedc73460593e5 100644 +index 4604a603c4ddd0a9242e859aaa5a511c2d4c4f84..84c7b89e7c894c0f544cf0ffcf9dff3f6a5919cc 100644 --- a/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java -@@ -23,7 +23,15 @@ public class NearestHealableRaiderTargetGoal extends Nea +@@ -23,12 +23,20 @@ public class NearestHealableRaiderTargetGoal extends Nea @Override public boolean canUse() { @@ -1688,6 +1702,12 @@ index 4604a603c4ddd0a9242e859aaa5a511c2d4c4f84..7274dfb52ca4a08cdebcd04294cedc73 return false; } else if (!((Raider)this.mob).hasActiveRaid()) { return false; + } else { +- this.findTarget(); ++ this.findTargetAsync(); // Leaf - Async target finding + return this.target != null; + } + } diff --git a/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java index abf57494950f55bbd75f335f26736cb9e703c197..efd2418f56c36e7850edde483a2a4906dd622441 100644 --- a/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java From 3e50c4fa8c74505a4691c20c8dd6079d98103224 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Sun, 25 May 2025 16:22:10 +0900 Subject: [PATCH 10/25] don't use the result when hasn't set waker --- leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java index 37e557ef..976de5f9 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java @@ -11,6 +11,9 @@ public class Waker { public boolean state = true; public final @Nullable Object result() { + if (state) { + return null; + } Object result = this.result; this.result = null; return result; From ccefbcfa8d3d55e001258bc571d3809e7287a8c8 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Mon, 26 May 2025 18:24:50 +0800 Subject: [PATCH 11/25] Update input sanitization for purpur config --- .../paperserver}/0002-Leaf-Bootstrap.patch | 2 ++ ...008-Fix-Pufferfish-and-Purpur-patches.patch | 18 ++++++++++++++++++ ...rable-movement-speed-of-more-entities.patch | 16 ++++++++-------- ...-missing-purpur-configuration-options.patch | 10 +++++----- ...Commands.patch => 0002-Leaf-Commands.patch} | 0 ...003-Pufferfish-Optimize-mob-spawning.patch} | 0 ... => 0004-Purpur-Server-Paper-Changes.patch} | 2 +- ...05-Fix-Pufferfish-and-Purpur-patches.patch} | 0 ...Timings.patch => 0006-Remove-Timings.patch} | 0 ....patch => 0007-KeYi-Player-Skull-API.patch} | 0 ...patch => 0008-Slice-Smooth-Teleports.patch} | 0 ...e.patch => 0009-Leaves-Protocol-Core.patch} | 0 ....patch => 0010-Leaves-Replay-Mod-API.patch} | 0 ...h => 0011-Skip-event-if-no-listeners.patch} | 0 ...p-EntityScheduler-s-executeTick-chec.patch} | 0 ...-SparklyPaper-Optimize-canSee-checks.patch} | 0 ...patch => 0014-Including-5s-in-getTPS.patch} | 0 ...ception-on-missing-ResourceKey-value.patch} | 0 ...6-Virtual-Thread-for-async-scheduler.patch} | 0 ...Configurable-chat-message-signatures.patch} | 0 ...eed.patch => 0018-Matter-Secure-Seed.patch} | 0 ...atch => 0019-Faster-random-generator.patch} | 0 ...Configurable-unknown-command-message.patch} | 2 +- ...-world-map-with-optimized-collection.patch} | 0 ...EntityType-minecraftToBukkit-convert.patch} | 0 ....patch => 0023-Multithreaded-Tracker.patch} | 2 +- ...r.patch => 0024-Asynchronous-locator.patch} | 0 ...e-snapshots-for-acquiring-blockstate.patch} | 2 +- ...-CraftServer-getworlds-list-creation.patch} | 0 ...nk-key.patch => 0027-Cache-chunk-key.patch} | 0 ...h => 0028-Async-structure-locate-api.patch} | 2 +- ...=> 0029-PlayerInventoryOverflowEvent.patch} | 0 ...-SparklyPaper-Parallel-world-ticking.patch} | 0 ...er-PR-Throttle-failed-spawn-attempts.patch} | 0 ...atch => 0032-Async-playerdata-saving.patch} | 0 ...erPR-Fix-save-load-NaN-Entity-Motion.patch} | 0 ...perPR-Fix-unnecessary-map-data-saves.patch} | 0 ...-send.patch => 0035-Async-chunk-send.patch} | 0 ...0036-Optimise-player-movement-checks.patch} | 0 ...ation.patch => 0037-Paw-optimization.patch} | 0 40 files changed, 38 insertions(+), 18 deletions(-) rename {leaf-server/paper-patches/features => leaf-archived-patches/removed/hardfork/paperserver}/0002-Leaf-Bootstrap.patch (97%) rename leaf-server/paper-patches/features/{0003-Leaf-Commands.patch => 0002-Leaf-Commands.patch} (100%) rename leaf-server/paper-patches/features/{0004-Pufferfish-Optimize-mob-spawning.patch => 0003-Pufferfish-Optimize-mob-spawning.patch} (100%) rename leaf-server/paper-patches/features/{0005-Purpur-Server-Paper-Changes.patch => 0004-Purpur-Server-Paper-Changes.patch} (99%) rename leaf-server/paper-patches/features/{0006-Fix-Pufferfish-and-Purpur-patches.patch => 0005-Fix-Pufferfish-and-Purpur-patches.patch} (100%) rename leaf-server/paper-patches/features/{0007-Remove-Timings.patch => 0006-Remove-Timings.patch} (100%) rename leaf-server/paper-patches/features/{0008-KeYi-Player-Skull-API.patch => 0007-KeYi-Player-Skull-API.patch} (100%) rename leaf-server/paper-patches/features/{0009-Slice-Smooth-Teleports.patch => 0008-Slice-Smooth-Teleports.patch} (100%) rename leaf-server/paper-patches/features/{0010-Leaves-Protocol-Core.patch => 0009-Leaves-Protocol-Core.patch} (100%) rename leaf-server/paper-patches/features/{0011-Leaves-Replay-Mod-API.patch => 0010-Leaves-Replay-Mod-API.patch} (100%) rename leaf-server/paper-patches/features/{0012-Skip-event-if-no-listeners.patch => 0011-Skip-event-if-no-listeners.patch} (100%) rename leaf-server/paper-patches/features/{0013-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch => 0012-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch} (100%) rename leaf-server/paper-patches/features/{0014-SparklyPaper-Optimize-canSee-checks.patch => 0013-SparklyPaper-Optimize-canSee-checks.patch} (100%) rename leaf-server/paper-patches/features/{0015-Including-5s-in-getTPS.patch => 0014-Including-5s-in-getTPS.patch} (100%) rename leaf-server/paper-patches/features/{0016-Don-t-throw-exception-on-missing-ResourceKey-value.patch => 0015-Don-t-throw-exception-on-missing-ResourceKey-value.patch} (100%) rename leaf-server/paper-patches/features/{0017-Virtual-Thread-for-async-scheduler.patch => 0016-Virtual-Thread-for-async-scheduler.patch} (100%) rename leaf-server/paper-patches/features/{0018-Mirai-Configurable-chat-message-signatures.patch => 0017-Mirai-Configurable-chat-message-signatures.patch} (100%) rename leaf-server/paper-patches/features/{0019-Matter-Secure-Seed.patch => 0018-Matter-Secure-Seed.patch} (100%) rename leaf-server/paper-patches/features/{0020-Faster-random-generator.patch => 0019-Faster-random-generator.patch} (100%) rename leaf-server/paper-patches/features/{0021-Configurable-unknown-command-message.patch => 0020-Configurable-unknown-command-message.patch} (94%) rename leaf-server/paper-patches/features/{0022-Replace-world-map-with-optimized-collection.patch => 0021-Replace-world-map-with-optimized-collection.patch} (100%) rename leaf-server/paper-patches/features/{0023-Cache-CraftEntityType-minecraftToBukkit-convert.patch => 0022-Cache-CraftEntityType-minecraftToBukkit-convert.patch} (100%) rename leaf-server/paper-patches/features/{0024-Multithreaded-Tracker.patch => 0023-Multithreaded-Tracker.patch} (98%) rename leaf-server/paper-patches/features/{0025-Asynchronous-locator.patch => 0024-Asynchronous-locator.patch} (100%) rename leaf-server/paper-patches/features/{0026-EMC-Don-t-use-snapshots-for-acquiring-blockstate.patch => 0025-EMC-Don-t-use-snapshots-for-acquiring-blockstate.patch} (94%) rename leaf-server/paper-patches/features/{0027-Faster-CraftServer-getworlds-list-creation.patch => 0026-Faster-CraftServer-getworlds-list-creation.patch} (100%) rename leaf-server/paper-patches/features/{0028-Cache-chunk-key.patch => 0027-Cache-chunk-key.patch} (100%) rename leaf-server/paper-patches/features/{0029-Async-structure-locate-api.patch => 0028-Async-structure-locate-api.patch} (97%) rename leaf-server/paper-patches/features/{0030-PlayerInventoryOverflowEvent.patch => 0029-PlayerInventoryOverflowEvent.patch} (100%) rename leaf-server/paper-patches/features/{0031-SparklyPaper-Parallel-world-ticking.patch => 0030-SparklyPaper-Parallel-world-ticking.patch} (100%) rename leaf-server/paper-patches/features/{0032-Paper-PR-Throttle-failed-spawn-attempts.patch => 0031-Paper-PR-Throttle-failed-spawn-attempts.patch} (100%) rename leaf-server/paper-patches/features/{0033-Async-playerdata-saving.patch => 0032-Async-playerdata-saving.patch} (100%) rename leaf-server/paper-patches/features/{0034-PaperPR-Fix-save-load-NaN-Entity-Motion.patch => 0033-PaperPR-Fix-save-load-NaN-Entity-Motion.patch} (100%) rename leaf-server/paper-patches/features/{0035-PaperPR-Fix-unnecessary-map-data-saves.patch => 0034-PaperPR-Fix-unnecessary-map-data-saves.patch} (100%) rename leaf-server/paper-patches/features/{0036-Async-chunk-send.patch => 0035-Async-chunk-send.patch} (100%) rename leaf-server/paper-patches/features/{0037-Optimise-player-movement-checks.patch => 0036-Optimise-player-movement-checks.patch} (100%) rename leaf-server/paper-patches/features/{0038-Paw-optimization.patch => 0037-Paw-optimization.patch} (100%) diff --git a/leaf-server/paper-patches/features/0002-Leaf-Bootstrap.patch b/leaf-archived-patches/removed/hardfork/paperserver/0002-Leaf-Bootstrap.patch similarity index 97% rename from leaf-server/paper-patches/features/0002-Leaf-Bootstrap.patch rename to leaf-archived-patches/removed/hardfork/paperserver/0002-Leaf-Bootstrap.patch index 06a070a3..049a33d3 100644 --- a/leaf-server/paper-patches/features/0002-Leaf-Bootstrap.patch +++ b/leaf-archived-patches/removed/hardfork/paperserver/0002-Leaf-Bootstrap.patch @@ -3,6 +3,8 @@ From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Wed, 31 Jul 2024 22:05:21 +0800 Subject: [PATCH] Leaf Bootstrap +Removed since Leaf 1.21.4, useless + org.bukkit.craftbukkit.Main#main -> LeafBootstrap -> PaperBootstrap -> ... diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java diff --git a/leaf-server/minecraft-patches/features/0008-Fix-Pufferfish-and-Purpur-patches.patch b/leaf-server/minecraft-patches/features/0008-Fix-Pufferfish-and-Purpur-patches.patch index 7f2591b9..5bdde608 100644 --- a/leaf-server/minecraft-patches/features/0008-Fix-Pufferfish-and-Purpur-patches.patch +++ b/leaf-server/minecraft-patches/features/0008-Fix-Pufferfish-and-Purpur-patches.patch @@ -322,3 +322,21 @@ index a33641dd6e0839fd1b557d8583fe8bb929fcc1cb..d5ef2ec5e15b6d250aafb0b8282b350c } else if (maxProjectileChunkLoadsConfig.perProjectile.resetMovementAfterReachLimit) { this.setDeltaMovement(0, this.getDeltaMovement().y, 0); } +diff --git a/org/purpurmc/purpur/PurpurWorldConfig.java b/org/purpurmc/purpur/PurpurWorldConfig.java +index b3fca75c66b16e35f6841c3b7df9103d68f1308e..2f2bd44723d3051ff7009f531f79471197705552 100644 +--- a/org/purpurmc/purpur/PurpurWorldConfig.java ++++ b/org/purpurmc/purpur/PurpurWorldConfig.java +@@ -2355,6 +2355,13 @@ public class PurpurWorldConfig { + piglinBypassMobGriefing = getBoolean("mobs.piglin.bypass-mob-griefing", piglinBypassMobGriefing); + piglinTakeDamageFromWater = getBoolean("mobs.piglin.takes-damage-from-water", piglinTakeDamageFromWater); + piglinPortalSpawnModifier = getInt("mobs.piglin.portal-spawn-modifier", piglinPortalSpawnModifier); ++ // Leaf start - Fix Pufferfish and Purpur patches - better input sanitization ++ if (piglinPortalSpawnModifier < 1) { ++ piglinPortalSpawnModifier = 1; ++ log(Level.WARNING, "mobs.piglin.portal-spawn-modifier is set to below minimum allowed value of 1"); ++ log(Level.WARNING, "Using value of 1 to prevent issues"); ++ } ++ // Leaf end - Fix Pufferfish and Purpur patches - better input sanitization + piglinAlwaysDropExp = getBoolean("mobs.piglin.always-drop-exp", piglinAlwaysDropExp); + piglinHeadVisibilityPercent = getDouble("mobs.piglin.head-visibility-percent", piglinHeadVisibilityPercent); + piglinIgnoresArmorWithGoldTrim = getBoolean("mobs.piglin.ignores-armor-with-gold-trim", piglinIgnoresArmorWithGoldTrim); diff --git a/leaf-server/minecraft-patches/features/0030-Configurable-movement-speed-of-more-entities.patch b/leaf-server/minecraft-patches/features/0030-Configurable-movement-speed-of-more-entities.patch index 61de77c8..e54544f0 100644 --- a/leaf-server/minecraft-patches/features/0030-Configurable-movement-speed-of-more-entities.patch +++ b/leaf-server/minecraft-patches/features/0030-Configurable-movement-speed-of-more-entities.patch @@ -50,7 +50,7 @@ index 7af71c777dca26cd94b1807a2a77ea0d30e92976..e64f9308fc848c0f22d8dbd6e544b786 .add(Attributes.ARMOR, 2.0) .add(Attributes.SPAWN_REINFORCEMENTS_CHANCE); diff --git a/net/minecraft/world/entity/monster/ZombieVillager.java b/net/minecraft/world/entity/monster/ZombieVillager.java -index 1ca0514732916d325c4a76d73120aaf613c3f780..970f6bdb0d0c15f7d814926472daec689beb82f4 100644 +index aa5c02b5c949c80a96c1dd60fd3de8e2261fe797..cb0e000c5b8636296c7d7474d0947c75f41b6058 100644 --- a/net/minecraft/world/entity/monster/ZombieVillager.java +++ b/net/minecraft/world/entity/monster/ZombieVillager.java @@ -99,6 +99,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { @@ -83,7 +83,7 @@ index fddbbffafea275dad187b7908386cf4c05c86743..89a67db9bbdb31661fa4f71f1270198b } diff --git a/org/purpurmc/purpur/PurpurWorldConfig.java b/org/purpurmc/purpur/PurpurWorldConfig.java -index b3fca75c66b16e35f6841c3b7df9103d68f1308e..ae06918ebfa3bbd914ebdebb8107ec447643ef22 100644 +index 2f2bd44723d3051ff7009f531f79471197705552..1d8b791c041139df7f24339849adf058936f8b57 100644 --- a/org/purpurmc/purpur/PurpurWorldConfig.java +++ b/org/purpurmc/purpur/PurpurWorldConfig.java @@ -1570,6 +1570,7 @@ public class PurpurWorldConfig { @@ -118,7 +118,7 @@ index b3fca75c66b16e35f6841c3b7df9103d68f1308e..ae06918ebfa3bbd914ebdebb8107ec44 } public boolean illusionerRidable = false; -@@ -3297,6 +3301,7 @@ public class PurpurWorldConfig { +@@ -3304,6 +3308,7 @@ public class PurpurWorldConfig { public boolean zombieTakeDamageFromWater = false; public boolean zombieAlwaysDropExp = false; public double zombieHeadVisibilityPercent = 0.5D; @@ -126,7 +126,7 @@ index b3fca75c66b16e35f6841c3b7df9103d68f1308e..ae06918ebfa3bbd914ebdebb8107ec44 private void zombieSettings() { zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable); zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater); -@@ -3317,6 +3322,7 @@ public class PurpurWorldConfig { +@@ -3324,6 +3329,7 @@ public class PurpurWorldConfig { zombieTakeDamageFromWater = getBoolean("mobs.zombie.takes-damage-from-water", zombieTakeDamageFromWater); zombieAlwaysDropExp = getBoolean("mobs.zombie.always-drop-exp", zombieAlwaysDropExp); zombieHeadVisibilityPercent = getDouble("mobs.zombie.head-visibility-percent", zombieHeadVisibilityPercent); @@ -134,7 +134,7 @@ index b3fca75c66b16e35f6841c3b7df9103d68f1308e..ae06918ebfa3bbd914ebdebb8107ec44 } public boolean zombieHorseRidable = false; -@@ -3366,6 +3372,7 @@ public class PurpurWorldConfig { +@@ -3373,6 +3379,7 @@ public class PurpurWorldConfig { public int zombieVillagerCuringTimeMax = 6000; public boolean zombieVillagerCureEnabled = true; public boolean zombieVillagerAlwaysDropExp = false; @@ -142,7 +142,7 @@ index b3fca75c66b16e35f6841c3b7df9103d68f1308e..ae06918ebfa3bbd914ebdebb8107ec44 private void zombieVillagerSettings() { zombieVillagerRidable = getBoolean("mobs.zombie_villager.ridable", zombieVillagerRidable); zombieVillagerRidableInWater = getBoolean("mobs.zombie_villager.ridable-in-water", zombieVillagerRidableInWater); -@@ -3386,6 +3393,7 @@ public class PurpurWorldConfig { +@@ -3393,6 +3400,7 @@ public class PurpurWorldConfig { zombieVillagerCuringTimeMax = getInt("mobs.zombie_villager.curing_time.max", zombieVillagerCuringTimeMax); zombieVillagerCureEnabled = getBoolean("mobs.zombie_villager.cure.enabled", zombieVillagerCureEnabled); zombieVillagerAlwaysDropExp = getBoolean("mobs.zombie_villager.always-drop-exp", zombieVillagerAlwaysDropExp); @@ -150,7 +150,7 @@ index b3fca75c66b16e35f6841c3b7df9103d68f1308e..ae06918ebfa3bbd914ebdebb8107ec44 } public boolean zombifiedPiglinRidable = false; -@@ -3400,6 +3408,7 @@ public class PurpurWorldConfig { +@@ -3407,6 +3415,7 @@ public class PurpurWorldConfig { public boolean zombifiedPiglinCountAsPlayerKillWhenAngry = true; public boolean zombifiedPiglinTakeDamageFromWater = false; public boolean zombifiedPiglinAlwaysDropExp = false; @@ -158,7 +158,7 @@ index b3fca75c66b16e35f6841c3b7df9103d68f1308e..ae06918ebfa3bbd914ebdebb8107ec44 private void zombifiedPiglinSettings() { zombifiedPiglinRidable = getBoolean("mobs.zombified_piglin.ridable", zombifiedPiglinRidable); zombifiedPiglinRidableInWater = getBoolean("mobs.zombified_piglin.ridable-in-water", zombifiedPiglinRidableInWater); -@@ -3418,6 +3427,7 @@ public class PurpurWorldConfig { +@@ -3425,6 +3434,7 @@ public class PurpurWorldConfig { zombifiedPiglinCountAsPlayerKillWhenAngry = getBoolean("mobs.zombified_piglin.count-as-player-kill-when-angry", zombifiedPiglinCountAsPlayerKillWhenAngry); zombifiedPiglinTakeDamageFromWater = getBoolean("mobs.zombified_piglin.takes-damage-from-water", zombifiedPiglinTakeDamageFromWater); zombifiedPiglinAlwaysDropExp = getBoolean("mobs.zombified_piglin.always-drop-exp", zombifiedPiglinAlwaysDropExp); diff --git a/leaf-server/minecraft-patches/features/0035-Plazma-Add-missing-purpur-configuration-options.patch b/leaf-server/minecraft-patches/features/0035-Plazma-Add-missing-purpur-configuration-options.patch index c8a6c4a3..1c4c6175 100644 --- a/leaf-server/minecraft-patches/features/0035-Plazma-Add-missing-purpur-configuration-options.patch +++ b/leaf-server/minecraft-patches/features/0035-Plazma-Add-missing-purpur-configuration-options.patch @@ -263,7 +263,7 @@ index 1cc5c37ee9aab6b9eb45881dddc03715bd4153b7..c0cde1461c0e3ec63bf77cc5056f8c63 org.bukkit.event.inventory.InventoryType.ENDER_CHEST.setDefaultSize(enderChestSixRows ? 54 : 27); enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows); diff --git a/org/purpurmc/purpur/PurpurWorldConfig.java b/org/purpurmc/purpur/PurpurWorldConfig.java -index ae06918ebfa3bbd914ebdebb8107ec447643ef22..793d2fd30edcf4c21445eabdab8d6813e460e469 100644 +index 1d8b791c041139df7f24339849adf058936f8b57..246a911915fee61e8fd459f13155740200019abc 100644 --- a/org/purpurmc/purpur/PurpurWorldConfig.java +++ b/org/purpurmc/purpur/PurpurWorldConfig.java @@ -1139,12 +1139,20 @@ public class PurpurWorldConfig { @@ -353,7 +353,7 @@ index ae06918ebfa3bbd914ebdebb8107ec447643ef22..793d2fd30edcf4c21445eabdab8d6813 } public boolean ghastRidable = false; -@@ -2801,6 +2835,10 @@ public class PurpurWorldConfig { +@@ -2808,6 +2842,10 @@ public class PurpurWorldConfig { public double snifferMaxHealth = 14.0D; public double snifferScale = 1.0D; public int snifferBreedingTicks = 6000; @@ -364,7 +364,7 @@ index ae06918ebfa3bbd914ebdebb8107ec447643ef22..793d2fd30edcf4c21445eabdab8d6813 private void snifferSettings() { snifferRidable = getBoolean("mobs.sniffer.ridable", snifferRidable); snifferRidableInWater = getBoolean("mobs.sniffer.ridable-in-water", snifferRidableInWater); -@@ -2808,6 +2846,10 @@ public class PurpurWorldConfig { +@@ -2815,6 +2853,10 @@ public class PurpurWorldConfig { snifferMaxHealth = getDouble("mobs.sniffer.attributes.max_health", snifferMaxHealth); snifferScale = Mth.clamp(getDouble("mobs.sniffer.attributes.scale", snifferScale), 0.0625D, 16.0D); snifferBreedingTicks = getInt("mobs.sniffer.breeding-delay-ticks", snifferBreedingTicks); @@ -375,7 +375,7 @@ index ae06918ebfa3bbd914ebdebb8107ec447643ef22..793d2fd30edcf4c21445eabdab8d6813 } public boolean squidRidable = false; -@@ -2909,10 +2951,20 @@ public class PurpurWorldConfig { +@@ -2916,10 +2958,20 @@ public class PurpurWorldConfig { public boolean tadpoleRidable = false; public boolean tadpoleRidableInWater = true; public boolean tadpoleControllable = true; @@ -396,7 +396,7 @@ index ae06918ebfa3bbd914ebdebb8107ec447643ef22..793d2fd30edcf4c21445eabdab8d6813 } public boolean traderLlamaRidable = false; -@@ -3141,10 +3193,20 @@ public class PurpurWorldConfig { +@@ -3148,10 +3200,20 @@ public class PurpurWorldConfig { public boolean wardenRidable = false; public boolean wardenRidableInWater = true; public boolean wardenControllable = true; diff --git a/leaf-server/paper-patches/features/0003-Leaf-Commands.patch b/leaf-server/paper-patches/features/0002-Leaf-Commands.patch similarity index 100% rename from leaf-server/paper-patches/features/0003-Leaf-Commands.patch rename to leaf-server/paper-patches/features/0002-Leaf-Commands.patch diff --git a/leaf-server/paper-patches/features/0004-Pufferfish-Optimize-mob-spawning.patch b/leaf-server/paper-patches/features/0003-Pufferfish-Optimize-mob-spawning.patch similarity index 100% rename from leaf-server/paper-patches/features/0004-Pufferfish-Optimize-mob-spawning.patch rename to leaf-server/paper-patches/features/0003-Pufferfish-Optimize-mob-spawning.patch diff --git a/leaf-server/paper-patches/features/0005-Purpur-Server-Paper-Changes.patch b/leaf-server/paper-patches/features/0004-Purpur-Server-Paper-Changes.patch similarity index 99% rename from leaf-server/paper-patches/features/0005-Purpur-Server-Paper-Changes.patch rename to leaf-server/paper-patches/features/0004-Purpur-Server-Paper-Changes.patch index 385589c1..da1e28e2 100644 --- a/leaf-server/paper-patches/features/0005-Purpur-Server-Paper-Changes.patch +++ b/leaf-server/paper-patches/features/0004-Purpur-Server-Paper-Changes.patch @@ -761,7 +761,7 @@ index a92e0877669a92851c6d7f83de75ffb087c8e651..daede6da974beb5ff19877caa5e6f8b3 public Collection getStructures(int x, int z) { return this.getStructures(x, z, struct -> true); diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index 70d093f71cca569d9da3ae82e738f8f3069f405e..7120ff469b0a0b638a4051b92f00f97da75edae0 100644 +index bf5343b0847e9f57ffbc7f33714ae6ca62f14332..2e1b7f613de8876095ef39bb0341a3f9520c8d5d 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -176,6 +176,13 @@ public class Main { diff --git a/leaf-server/paper-patches/features/0006-Fix-Pufferfish-and-Purpur-patches.patch b/leaf-server/paper-patches/features/0005-Fix-Pufferfish-and-Purpur-patches.patch similarity index 100% rename from leaf-server/paper-patches/features/0006-Fix-Pufferfish-and-Purpur-patches.patch rename to leaf-server/paper-patches/features/0005-Fix-Pufferfish-and-Purpur-patches.patch diff --git a/leaf-server/paper-patches/features/0007-Remove-Timings.patch b/leaf-server/paper-patches/features/0006-Remove-Timings.patch similarity index 100% rename from leaf-server/paper-patches/features/0007-Remove-Timings.patch rename to leaf-server/paper-patches/features/0006-Remove-Timings.patch diff --git a/leaf-server/paper-patches/features/0008-KeYi-Player-Skull-API.patch b/leaf-server/paper-patches/features/0007-KeYi-Player-Skull-API.patch similarity index 100% rename from leaf-server/paper-patches/features/0008-KeYi-Player-Skull-API.patch rename to leaf-server/paper-patches/features/0007-KeYi-Player-Skull-API.patch diff --git a/leaf-server/paper-patches/features/0009-Slice-Smooth-Teleports.patch b/leaf-server/paper-patches/features/0008-Slice-Smooth-Teleports.patch similarity index 100% rename from leaf-server/paper-patches/features/0009-Slice-Smooth-Teleports.patch rename to leaf-server/paper-patches/features/0008-Slice-Smooth-Teleports.patch diff --git a/leaf-server/paper-patches/features/0010-Leaves-Protocol-Core.patch b/leaf-server/paper-patches/features/0009-Leaves-Protocol-Core.patch similarity index 100% rename from leaf-server/paper-patches/features/0010-Leaves-Protocol-Core.patch rename to leaf-server/paper-patches/features/0009-Leaves-Protocol-Core.patch diff --git a/leaf-server/paper-patches/features/0011-Leaves-Replay-Mod-API.patch b/leaf-server/paper-patches/features/0010-Leaves-Replay-Mod-API.patch similarity index 100% rename from leaf-server/paper-patches/features/0011-Leaves-Replay-Mod-API.patch rename to leaf-server/paper-patches/features/0010-Leaves-Replay-Mod-API.patch diff --git a/leaf-server/paper-patches/features/0012-Skip-event-if-no-listeners.patch b/leaf-server/paper-patches/features/0011-Skip-event-if-no-listeners.patch similarity index 100% rename from leaf-server/paper-patches/features/0012-Skip-event-if-no-listeners.patch rename to leaf-server/paper-patches/features/0011-Skip-event-if-no-listeners.patch diff --git a/leaf-server/paper-patches/features/0013-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch b/leaf-server/paper-patches/features/0012-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch similarity index 100% rename from leaf-server/paper-patches/features/0013-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch rename to leaf-server/paper-patches/features/0012-SparklyPaper-Skip-EntityScheduler-s-executeTick-chec.patch diff --git a/leaf-server/paper-patches/features/0014-SparklyPaper-Optimize-canSee-checks.patch b/leaf-server/paper-patches/features/0013-SparklyPaper-Optimize-canSee-checks.patch similarity index 100% rename from leaf-server/paper-patches/features/0014-SparklyPaper-Optimize-canSee-checks.patch rename to leaf-server/paper-patches/features/0013-SparklyPaper-Optimize-canSee-checks.patch diff --git a/leaf-server/paper-patches/features/0015-Including-5s-in-getTPS.patch b/leaf-server/paper-patches/features/0014-Including-5s-in-getTPS.patch similarity index 100% rename from leaf-server/paper-patches/features/0015-Including-5s-in-getTPS.patch rename to leaf-server/paper-patches/features/0014-Including-5s-in-getTPS.patch diff --git a/leaf-server/paper-patches/features/0016-Don-t-throw-exception-on-missing-ResourceKey-value.patch b/leaf-server/paper-patches/features/0015-Don-t-throw-exception-on-missing-ResourceKey-value.patch similarity index 100% rename from leaf-server/paper-patches/features/0016-Don-t-throw-exception-on-missing-ResourceKey-value.patch rename to leaf-server/paper-patches/features/0015-Don-t-throw-exception-on-missing-ResourceKey-value.patch diff --git a/leaf-server/paper-patches/features/0017-Virtual-Thread-for-async-scheduler.patch b/leaf-server/paper-patches/features/0016-Virtual-Thread-for-async-scheduler.patch similarity index 100% rename from leaf-server/paper-patches/features/0017-Virtual-Thread-for-async-scheduler.patch rename to leaf-server/paper-patches/features/0016-Virtual-Thread-for-async-scheduler.patch diff --git a/leaf-server/paper-patches/features/0018-Mirai-Configurable-chat-message-signatures.patch b/leaf-server/paper-patches/features/0017-Mirai-Configurable-chat-message-signatures.patch similarity index 100% rename from leaf-server/paper-patches/features/0018-Mirai-Configurable-chat-message-signatures.patch rename to leaf-server/paper-patches/features/0017-Mirai-Configurable-chat-message-signatures.patch diff --git a/leaf-server/paper-patches/features/0019-Matter-Secure-Seed.patch b/leaf-server/paper-patches/features/0018-Matter-Secure-Seed.patch similarity index 100% rename from leaf-server/paper-patches/features/0019-Matter-Secure-Seed.patch rename to leaf-server/paper-patches/features/0018-Matter-Secure-Seed.patch diff --git a/leaf-server/paper-patches/features/0020-Faster-random-generator.patch b/leaf-server/paper-patches/features/0019-Faster-random-generator.patch similarity index 100% rename from leaf-server/paper-patches/features/0020-Faster-random-generator.patch rename to leaf-server/paper-patches/features/0019-Faster-random-generator.patch diff --git a/leaf-server/paper-patches/features/0021-Configurable-unknown-command-message.patch b/leaf-server/paper-patches/features/0020-Configurable-unknown-command-message.patch similarity index 94% rename from leaf-server/paper-patches/features/0021-Configurable-unknown-command-message.patch rename to leaf-server/paper-patches/features/0020-Configurable-unknown-command-message.patch index ca7402c7..6a251818 100644 --- a/leaf-server/paper-patches/features/0021-Configurable-unknown-command-message.patch +++ b/leaf-server/paper-patches/features/0020-Configurable-unknown-command-message.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Configurable unknown command message diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java -index e0d4222a99f22d7130d95cf29b034a98f2f3b76e..089dd39d428bd1e3773769f2a50cc2f3bc1b4311 100644 +index d1351ed25c4de3e4989a47a793047b0614e8d0b2..0de990f5b90c3404cfbf52d3e75b3c0a3a98de14 100644 --- a/src/main/java/org/spigotmc/SpigotConfig.java +++ b/src/main/java/org/spigotmc/SpigotConfig.java @@ -151,7 +151,6 @@ public class SpigotConfig { diff --git a/leaf-server/paper-patches/features/0022-Replace-world-map-with-optimized-collection.patch b/leaf-server/paper-patches/features/0021-Replace-world-map-with-optimized-collection.patch similarity index 100% rename from leaf-server/paper-patches/features/0022-Replace-world-map-with-optimized-collection.patch rename to leaf-server/paper-patches/features/0021-Replace-world-map-with-optimized-collection.patch diff --git a/leaf-server/paper-patches/features/0023-Cache-CraftEntityType-minecraftToBukkit-convert.patch b/leaf-server/paper-patches/features/0022-Cache-CraftEntityType-minecraftToBukkit-convert.patch similarity index 100% rename from leaf-server/paper-patches/features/0023-Cache-CraftEntityType-minecraftToBukkit-convert.patch rename to leaf-server/paper-patches/features/0022-Cache-CraftEntityType-minecraftToBukkit-convert.patch diff --git a/leaf-server/paper-patches/features/0024-Multithreaded-Tracker.patch b/leaf-server/paper-patches/features/0023-Multithreaded-Tracker.patch similarity index 98% rename from leaf-server/paper-patches/features/0024-Multithreaded-Tracker.patch rename to leaf-server/paper-patches/features/0023-Multithreaded-Tracker.patch index 2d96ec10..39c5b084 100644 --- a/leaf-server/paper-patches/features/0024-Multithreaded-Tracker.patch +++ b/leaf-server/paper-patches/features/0023-Multithreaded-Tracker.patch @@ -41,7 +41,7 @@ index 379c2dc1853e45a96dda9b13bf28b7e08f65658a..361f4de9cdf0f7505628a2fed2a3f536 } // Leaves start - skip photographer diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index edcd209798740f31cb302f36d7864a0d8ea1d561..e2444cc9e28dd432bf3351066b1408102decfa0a 100644 +index edcd209798740f31cb302f36d7864a0d8ea1d561..e8b80ac3d2a54b9e855cad80c8a782007f5ea726 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -749,7 +749,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { diff --git a/leaf-server/paper-patches/features/0025-Asynchronous-locator.patch b/leaf-server/paper-patches/features/0024-Asynchronous-locator.patch similarity index 100% rename from leaf-server/paper-patches/features/0025-Asynchronous-locator.patch rename to leaf-server/paper-patches/features/0024-Asynchronous-locator.patch diff --git a/leaf-server/paper-patches/features/0026-EMC-Don-t-use-snapshots-for-acquiring-blockstate.patch b/leaf-server/paper-patches/features/0025-EMC-Don-t-use-snapshots-for-acquiring-blockstate.patch similarity index 94% rename from leaf-server/paper-patches/features/0026-EMC-Don-t-use-snapshots-for-acquiring-blockstate.patch rename to leaf-server/paper-patches/features/0025-EMC-Don-t-use-snapshots-for-acquiring-blockstate.patch index 4aee5897..7c00440e 100644 --- a/leaf-server/paper-patches/features/0026-EMC-Don-t-use-snapshots-for-acquiring-blockstate.patch +++ b/leaf-server/paper-patches/features/0025-EMC-Don-t-use-snapshots-for-acquiring-blockstate.patch @@ -7,7 +7,7 @@ Original license: MIT Original project: https://github.com/starlis/empirecraft diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index 5cb69d0b822e11a99a96aef4f59986d083b079f4..973b297a22c0cc53f966582c67c3688f4b2205c7 100644 +index 02e8a49d220ad99f7a5dc9786617f3759c3ca0d0..811823a1a7e24a19a7e37eb4c08efdfa19e839ed 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java @@ -331,7 +331,7 @@ public class CraftBlock implements Block { diff --git a/leaf-server/paper-patches/features/0027-Faster-CraftServer-getworlds-list-creation.patch b/leaf-server/paper-patches/features/0026-Faster-CraftServer-getworlds-list-creation.patch similarity index 100% rename from leaf-server/paper-patches/features/0027-Faster-CraftServer-getworlds-list-creation.patch rename to leaf-server/paper-patches/features/0026-Faster-CraftServer-getworlds-list-creation.patch diff --git a/leaf-server/paper-patches/features/0028-Cache-chunk-key.patch b/leaf-server/paper-patches/features/0027-Cache-chunk-key.patch similarity index 100% rename from leaf-server/paper-patches/features/0028-Cache-chunk-key.patch rename to leaf-server/paper-patches/features/0027-Cache-chunk-key.patch diff --git a/leaf-server/paper-patches/features/0029-Async-structure-locate-api.patch b/leaf-server/paper-patches/features/0028-Async-structure-locate-api.patch similarity index 97% rename from leaf-server/paper-patches/features/0029-Async-structure-locate-api.patch rename to leaf-server/paper-patches/features/0028-Async-structure-locate-api.patch index fa396fc3..41d95408 100644 --- a/leaf-server/paper-patches/features/0029-Async-structure-locate-api.patch +++ b/leaf-server/paper-patches/features/0028-Async-structure-locate-api.patch @@ -9,7 +9,7 @@ Added some asynchronous structure locate methods in World, requires async-locator to be enabled in Leaf config, or else it will fall back to sync methods. diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index ca60f91ef012c94174a0803eb77699ba9ecff5e1..15673166e566b2a6d5093210d99b154e69fab0ad 100644 +index daede6da974beb5ff19877caa5e6f8b3ecdf9000..af33cab59932f4ec135caf94dc5828930833daf6 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -2271,6 +2271,45 @@ public class CraftWorld extends CraftRegionAccessor implements World { diff --git a/leaf-server/paper-patches/features/0030-PlayerInventoryOverflowEvent.patch b/leaf-server/paper-patches/features/0029-PlayerInventoryOverflowEvent.patch similarity index 100% rename from leaf-server/paper-patches/features/0030-PlayerInventoryOverflowEvent.patch rename to leaf-server/paper-patches/features/0029-PlayerInventoryOverflowEvent.patch diff --git a/leaf-server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch b/leaf-server/paper-patches/features/0030-SparklyPaper-Parallel-world-ticking.patch similarity index 100% rename from leaf-server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch rename to leaf-server/paper-patches/features/0030-SparklyPaper-Parallel-world-ticking.patch diff --git a/leaf-server/paper-patches/features/0032-Paper-PR-Throttle-failed-spawn-attempts.patch b/leaf-server/paper-patches/features/0031-Paper-PR-Throttle-failed-spawn-attempts.patch similarity index 100% rename from leaf-server/paper-patches/features/0032-Paper-PR-Throttle-failed-spawn-attempts.patch rename to leaf-server/paper-patches/features/0031-Paper-PR-Throttle-failed-spawn-attempts.patch diff --git a/leaf-server/paper-patches/features/0033-Async-playerdata-saving.patch b/leaf-server/paper-patches/features/0032-Async-playerdata-saving.patch similarity index 100% rename from leaf-server/paper-patches/features/0033-Async-playerdata-saving.patch rename to leaf-server/paper-patches/features/0032-Async-playerdata-saving.patch diff --git a/leaf-server/paper-patches/features/0034-PaperPR-Fix-save-load-NaN-Entity-Motion.patch b/leaf-server/paper-patches/features/0033-PaperPR-Fix-save-load-NaN-Entity-Motion.patch similarity index 100% rename from leaf-server/paper-patches/features/0034-PaperPR-Fix-save-load-NaN-Entity-Motion.patch rename to leaf-server/paper-patches/features/0033-PaperPR-Fix-save-load-NaN-Entity-Motion.patch diff --git a/leaf-server/paper-patches/features/0035-PaperPR-Fix-unnecessary-map-data-saves.patch b/leaf-server/paper-patches/features/0034-PaperPR-Fix-unnecessary-map-data-saves.patch similarity index 100% rename from leaf-server/paper-patches/features/0035-PaperPR-Fix-unnecessary-map-data-saves.patch rename to leaf-server/paper-patches/features/0034-PaperPR-Fix-unnecessary-map-data-saves.patch diff --git a/leaf-server/paper-patches/features/0036-Async-chunk-send.patch b/leaf-server/paper-patches/features/0035-Async-chunk-send.patch similarity index 100% rename from leaf-server/paper-patches/features/0036-Async-chunk-send.patch rename to leaf-server/paper-patches/features/0035-Async-chunk-send.patch diff --git a/leaf-server/paper-patches/features/0037-Optimise-player-movement-checks.patch b/leaf-server/paper-patches/features/0036-Optimise-player-movement-checks.patch similarity index 100% rename from leaf-server/paper-patches/features/0037-Optimise-player-movement-checks.patch rename to leaf-server/paper-patches/features/0036-Optimise-player-movement-checks.patch diff --git a/leaf-server/paper-patches/features/0038-Paw-optimization.patch b/leaf-server/paper-patches/features/0037-Paw-optimization.patch similarity index 100% rename from leaf-server/paper-patches/features/0038-Paw-optimization.patch rename to leaf-server/paper-patches/features/0037-Paw-optimization.patch From 519ddbf58bff7d69a8691ff09f5c1bee0516940c Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Mon, 26 May 2025 19:46:57 +0900 Subject: [PATCH 12/25] remove SendChanges type cast --- .../dreeam/leaf/async/tracker/MultithreadedTracker.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java b/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java index a62ea7a9..e1ce6caa 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java @@ -32,11 +32,11 @@ public class MultithreadedTracker { private static long lastWarnMillis = System.currentTimeMillis(); private static ThreadPoolExecutor TRACKER_EXECUTOR = null; - private record SendChanges(Object[] entities, int size) implements Runnable { + private record SendChanges(ServerEntity[] entities, int size) implements Runnable { @Override public void run() { for (int i = 0; i < size; i++) { - ((ServerEntity) entities[i]).sendDirtyEntityData(); + entities[i].sendDirtyEntityData(); } } } @@ -80,7 +80,7 @@ public class MultithreadedTracker { // Move tracking to off-main TRACKER_EXECUTOR.execute(() -> { - ReferenceArrayList sendDirty = new ReferenceArrayList<>(); + ReferenceArrayList sendDirty = ReferenceArrayList.wrap(new ServerEntity[0]); for (final Entity entity : trackerEntitiesRaw) { if (entity == null) continue; @@ -141,7 +141,7 @@ public class MultithreadedTracker { sendChanges.run(); } - ReferenceArrayList sendDirty = new ReferenceArrayList<>(); + ReferenceArrayList sendDirty = ReferenceArrayList.wrap(new ServerEntity[0]);; for (final Entity entity : trackerEntitiesRaw) { if (entity == null) continue; From c1297d728e4288bfa303ff48e1a590e800ffc752 Mon Sep 17 00:00:00 2001 From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com> Date: Tue, 27 May 2025 04:05:52 +1400 Subject: [PATCH 13/25] Remove redundant full-qualified name & Higher priority for async tasks --- .../java/org/dreeam/leaf/async/chunk/AsyncChunkSend.java | 7 ++++--- .../org/dreeam/leaf/async/path/AsyncPathProcessor.java | 9 +++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/chunk/AsyncChunkSend.java b/leaf-server/src/main/java/org/dreeam/leaf/async/chunk/AsyncChunkSend.java index d5fcecaf..55f40ca1 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/chunk/AsyncChunkSend.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/chunk/AsyncChunkSend.java @@ -1,5 +1,6 @@ package org.dreeam.leaf.async.chunk; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import net.minecraft.Util; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -14,13 +15,13 @@ public class AsyncChunkSend { public static final ExecutorService POOL = new ThreadPoolExecutor( 1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), - new com.google.common.util.concurrent.ThreadFactoryBuilder() - .setPriority(Thread.NORM_PRIORITY - 2) + new ThreadFactoryBuilder() + .setPriority(Thread.NORM_PRIORITY) .setNameFormat("Leaf Async Chunk Send Thread") .setUncaughtExceptionHandler(Util::onThreadException) .setThreadFactory(AsyncChunkSendThread::new) .build(), - new ThreadPoolExecutor.DiscardPolicy() + new ThreadPoolExecutor.CallerRunsPolicy() ); public static final Logger LOGGER = LogManager.getLogger("Leaf Async Chunk Send"); } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java index 12ae72fe..7ebcec15 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java @@ -5,6 +5,7 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.pathfinder.Path; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dreeam.leaf.config.modules.async.AsyncPathfinding; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -29,8 +30,8 @@ public class AsyncPathProcessor { private static long lastWarnMillis = System.currentTimeMillis(); private static final ThreadPoolExecutor pathProcessingExecutor = new ThreadPoolExecutor( 1, - org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingMaxThreads, - org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingKeepalive, TimeUnit.SECONDS, + AsyncPathfinding.asyncPathfindingMaxThreads, + AsyncPathfinding.asyncPathfindingKeepalive, TimeUnit.SECONDS, getQueueImpl(), new ThreadFactoryBuilder() .setNameFormat(THREAD_PREFIX + " Thread - %d") @@ -44,7 +45,7 @@ public class AsyncPathProcessor { public void rejectedExecution(Runnable rejectedTask, ThreadPoolExecutor executor) { BlockingQueue workQueue = executor.getQueue(); if (!executor.isShutdown()) { - switch (org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingRejectPolicy) { + switch (AsyncPathfinding.asyncPathfindingRejectPolicy) { case FLUSH_ALL -> { if (!workQueue.isEmpty()) { List pendingTasks = new ArrayList<>(workQueue.size()); @@ -98,7 +99,7 @@ public class AsyncPathProcessor { } private static BlockingQueue getQueueImpl() { - final int queueCapacity = org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingQueueSize; + final int queueCapacity = AsyncPathfinding.asyncPathfindingQueueSize; return new LinkedBlockingQueue<>(queueCapacity); } From 42a2c8d2f6aaf2f8889c0760c4d9f02ccdf0fabe Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Wed, 28 May 2025 01:19:57 +0800 Subject: [PATCH 14/25] Cleanup version fetcher --- .../gale-patches/features/0001-Rebrand.patch | 51 +++++++++++++++++++ ...ommands.patch => 0002-Leaf-Commands.patch} | 0 ...3-Fix-Pufferfish-and-Purpur-patches.patch} | 6 +-- ...le-arrow-despawn-counter-by-default.patch} | 0 ...e-items-finding-hopper-nearby-check.patch} | 0 .../paper-patches/features/0001-Rebrand.patch | 14 ++++- .../leaf/version/LeafVersionFetcher.java | 5 +- 7 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 leaf-server/gale-patches/features/0001-Rebrand.patch rename leaf-server/gale-patches/features/{0001-Leaf-Commands.patch => 0002-Leaf-Commands.patch} (100%) rename leaf-server/gale-patches/features/{0002-Fix-Pufferfish-and-Purpur-patches.patch => 0003-Fix-Pufferfish-and-Purpur-patches.patch} (88%) rename leaf-server/gale-patches/features/{0003-KeYi-Disable-arrow-despawn-counter-by-default.patch => 0004-KeYi-Disable-arrow-despawn-counter-by-default.patch} (100%) rename leaf-server/gale-patches/features/{0004-Reduce-active-items-finding-hopper-nearby-check.patch => 0005-Reduce-active-items-finding-hopper-nearby-check.patch} (100%) diff --git a/leaf-server/gale-patches/features/0001-Rebrand.patch b/leaf-server/gale-patches/features/0001-Rebrand.patch new file mode 100644 index 00000000..9beacdee --- /dev/null +++ b/leaf-server/gale-patches/features/0001-Rebrand.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Wed, 28 May 2025 01:17:34 +0800 +Subject: [PATCH] Rebrand + + +diff --git a/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java b/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java +index 38ecc4a0ea6e9a7f4c3a01077b7fc6f04fa20d80..fcaab9c6e1bab9b6fda8d7e1b4a68711f46aeb32 100644 +--- a/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java ++++ b/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java +@@ -40,18 +40,15 @@ public abstract class AbstractPaperVersionFetcher implements VersionFetcher { + protected static final Logger LOGGER = LogUtils.getClassLogger(); + protected static final int DISTANCE_ERROR = -1; + protected static final int DISTANCE_UNKNOWN = -2; +- protected static final ServerBuildInfo BUILD_INFO = ServerBuildInfo.buildInfo(); + + // Gale start - branding changes - version fetcher +- protected final String gitHubBranchName; + protected final String downloadPage; + protected final String organizationDisplayName; + protected final String projectDisplayName; + protected final String gitHubOrganizationName; + protected final String gitHubRepoName; + +- protected AbstractPaperVersionFetcher(String githubBranchName, String downloadPage, String organizationDisplayName, String projectDisplayName, String gitHubOrganizationName, String gitHubRepoName) { +- this.gitHubBranchName = githubBranchName; ++ protected AbstractPaperVersionFetcher(String downloadPage, String organizationDisplayName, String projectDisplayName, String gitHubOrganizationName, String gitHubRepoName) { + this.downloadPage = downloadPage; + this.organizationDisplayName = organizationDisplayName; + this.projectDisplayName = projectDisplayName; +@@ -69,7 +66,7 @@ public abstract class AbstractPaperVersionFetcher implements VersionFetcher { + public @NotNull Component getVersionMessage(final @NotNull String serverVersion) { + final Component updateMessage; + final ServerBuildInfo build = ServerBuildInfo.buildInfo(); +- if (build.buildNumber().isEmpty() && build.gitCommit().isEmpty()) { ++ if (build.buildNumber().isEmpty() || build.gitCommit().isEmpty()) { // Gale - branding changes - version fetcher + updateMessage = text("You are running a development version without access to version information", color(0xFF5300)); + } else { + updateMessage = getUpdateStatusMessage(this.gitHubOrganizationName + "/" + this.gitHubRepoName, build); // Gale - branding changes - version fetcher +diff --git a/src/main/java/org/galemc/gale/version/GaleVersionFetcher.java b/src/main/java/org/galemc/gale/version/GaleVersionFetcher.java +index 13de2359d321f2cebdc3a6d179ca27fb43ea5891..0d48c7b58efe3a01822ad207b44bdedaae5d4279 100644 +--- a/src/main/java/org/galemc/gale/version/GaleVersionFetcher.java ++++ b/src/main/java/org/galemc/gale/version/GaleVersionFetcher.java +@@ -6,7 +6,6 @@ public class GaleVersionFetcher extends AbstractPaperVersionFetcher { + + public GaleVersionFetcher() { + super( +- "ver/1.21.4", + "https://github.com/Dreeam-qwq/Gale", + "GaleMC", + "Gale", diff --git a/leaf-server/gale-patches/features/0001-Leaf-Commands.patch b/leaf-server/gale-patches/features/0002-Leaf-Commands.patch similarity index 100% rename from leaf-server/gale-patches/features/0001-Leaf-Commands.patch rename to leaf-server/gale-patches/features/0002-Leaf-Commands.patch diff --git a/leaf-server/gale-patches/features/0002-Fix-Pufferfish-and-Purpur-patches.patch b/leaf-server/gale-patches/features/0003-Fix-Pufferfish-and-Purpur-patches.patch similarity index 88% rename from leaf-server/gale-patches/features/0002-Fix-Pufferfish-and-Purpur-patches.patch rename to leaf-server/gale-patches/features/0003-Fix-Pufferfish-and-Purpur-patches.patch index dc891ce8..40414843 100644 --- a/leaf-server/gale-patches/features/0002-Fix-Pufferfish-and-Purpur-patches.patch +++ b/leaf-server/gale-patches/features/0003-Fix-Pufferfish-and-Purpur-patches.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix Pufferfish and Purpur patches diff --git a/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java b/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java -index 38ecc4a0ea6e9a7f4c3a01077b7fc6f04fa20d80..6c99f64eca1fa68bb4714021cefb999dcd335b21 100644 +index fcaab9c6e1bab9b6fda8d7e1b4a68711f46aeb32..8fd2157faece39024ef3bdc3000684c6b368091e 100644 --- a/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java +++ b/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java -@@ -101,10 +101,10 @@ public abstract class AbstractPaperVersionFetcher implements VersionFetcher { +@@ -98,10 +98,10 @@ public abstract class AbstractPaperVersionFetcher implements VersionFetcher { // Gale end - branding changes - version fetcher return switch (distance) { @@ -23,7 +23,7 @@ index 38ecc4a0ea6e9a7f4c3a01077b7fc6f04fa20d80..6c99f64eca1fa68bb4714021cefb999d .append(Component.newline()) .append(text("Download the new version at: ") .append(text(this.downloadPage, NamedTextColor.GOLD) // Gale - branding changes - version fetcher -@@ -149,6 +149,6 @@ public abstract class AbstractPaperVersionFetcher implements VersionFetcher { +@@ -146,6 +146,6 @@ public abstract class AbstractPaperVersionFetcher implements VersionFetcher { return null; } diff --git a/leaf-server/gale-patches/features/0003-KeYi-Disable-arrow-despawn-counter-by-default.patch b/leaf-server/gale-patches/features/0004-KeYi-Disable-arrow-despawn-counter-by-default.patch similarity index 100% rename from leaf-server/gale-patches/features/0003-KeYi-Disable-arrow-despawn-counter-by-default.patch rename to leaf-server/gale-patches/features/0004-KeYi-Disable-arrow-despawn-counter-by-default.patch diff --git a/leaf-server/gale-patches/features/0004-Reduce-active-items-finding-hopper-nearby-check.patch b/leaf-server/gale-patches/features/0005-Reduce-active-items-finding-hopper-nearby-check.patch similarity index 100% rename from leaf-server/gale-patches/features/0004-Reduce-active-items-finding-hopper-nearby-check.patch rename to leaf-server/gale-patches/features/0005-Reduce-active-items-finding-hopper-nearby-check.patch diff --git a/leaf-server/paper-patches/features/0001-Rebrand.patch b/leaf-server/paper-patches/features/0001-Rebrand.patch index 9a49e32a..df988083 100644 --- a/leaf-server/paper-patches/features/0001-Rebrand.patch +++ b/leaf-server/paper-patches/features/0001-Rebrand.patch @@ -37,6 +37,18 @@ index 3d8ed4ff9a5a30d123508aeb485250271b528a6e..49ac2b52fc134bfa77a108bf58de3e81 metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { Map> map = new HashMap<>(2); // Gale - metrics - reduce HashMap capacity +diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +index 7fab7113821dc5d5f4ba4a2e0ee25968ad5a4439..eac1a227b50e9de07eac26dcfdba4055152897bc 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java ++++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +@@ -40,7 +40,6 @@ public class PaperVersionFetcher extends org.galemc.gale.version.AbstractPaperVe + + public PaperVersionFetcher() { + super( +- "main", + "https://papermc.io/downloads/paper", + "PaperMC", + "Paper", diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java index b78828e83d8128eace986aeb73213da3b3f905e4..f59879ae7d3c73cd4c4233d30667988bf22f12f1 100644 --- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java @@ -103,7 +115,7 @@ index f6bc955c3496b52cda1a20aabd78769803ef471f..f6f787db016f4d1ac14648ebf2b8e162 // (async tasks must live with race-conditions if they attempt to cancel between these few lines of code) } diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index ebe1136e2487d0d13e5a924a644f2237900a86a6..bef2ae29856a72344d3d755926f1b2dce4907df1 100644 +index 5155b3db9f723c4798d9964e6584654ba7c3368a..d4012acbad76ef0389d176a01e42a4c27e1918bc 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java @@ -508,7 +508,7 @@ public final class CraftMagicNumbers implements UnsafeValues { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/version/LeafVersionFetcher.java b/leaf-server/src/main/java/org/dreeam/leaf/version/LeafVersionFetcher.java index 496201a0..16748a24 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/version/LeafVersionFetcher.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/version/LeafVersionFetcher.java @@ -6,9 +6,8 @@ public class LeafVersionFetcher extends AbstractPaperVersionFetcher { public LeafVersionFetcher() { super( - "ver/1.21.4", - "https://github.com/Winds-Studio/Leaf", - "Winds-Studio", + "https://www.leafmc.one/download", + "Winds Studio", "Leaf", "Winds-Studio", "Leaf" From 2ed7bde9fce7327bf0f3c07a5d87dc5ea6b3f1fb Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Wed, 28 May 2025 11:02:45 +0900 Subject: [PATCH 15/25] Revert "don't use the result when hasn't set waker" This reverts commit 3e50c4fa8c74505a4691c20c8dd6079d98103224. --- leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java index 976de5f9..37e557ef 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/Waker.java @@ -11,9 +11,6 @@ public class Waker { public boolean state = true; public final @Nullable Object result() { - if (state) { - return null; - } Object result = this.result; this.result = null; return result; From d8d601b9f663228c131a3599896ed5af930b6a78 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Wed, 28 May 2025 17:12:47 +0900 Subject: [PATCH 16/25] cleanup & remove lockedFlags#removeIf stream --- .../features/0153-Async-target-finding.patch | 98 ++++++++++--------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch index 684ffe8b..c714bf57 100644 --- a/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch @@ -307,14 +307,14 @@ index 05d5cde42b7011091ef4ee874c0d9d5586ae3f10..88809afe30bb970a7de8bdfd26926880 this.navigation.tick(); this.customServerAiStep((ServerLevel)this.level()); diff --git a/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java b/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java -index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..9abb8e7b0dea2cb63dad234812d773403d0716f6 100644 +index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..22f052d02ccf22f22894a0b236af1b95c8d65e97 100644 --- a/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java +++ b/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java @@ -67,15 +67,24 @@ public class AvoidEntityGoal extends Goal { @Override public boolean canUse() { -+ // Leaf start - Async Avoid Entity Finding ++ // Leaf start - Async target finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + if (!poll()) { + getNearestEntityAsync(); @@ -328,11 +328,11 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..9abb8e7b0dea2cb63dad234812d77340 this.mob, this.mob.getX(), - this.mob.getY(), -+ this.mob.getEyeY(), // Leaf - Async Avoid Entity Finding ++ this.mob.getEyeY(), // Leaf - Async target finding this.mob.getZ() ); + } -+ // Leaf end - Async Avoid Entity Finding ++ // Leaf end - Async target finding if (this.toAvoid == null) { return false; } else { @@ -340,7 +340,7 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..9abb8e7b0dea2cb63dad234812d77340 } } -+ // Leaf start - Async Avoid Entity Finding ++ // Leaf start - Async target finding + private boolean poll() { + if (!(this.mob.getGoalCtx().result() instanceof LivingEntity target)) return false; + var serverLevel = getServerLevel(this.mob); @@ -368,25 +368,25 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..9abb8e7b0dea2cb63dad234812d77340 + z + ); + } -+ // Leaf end - Async Avoid Entity Finding ++ // Leaf end - Async target finding + @Override public boolean canContinueToUse() { return !this.pathNav.isDone(); diff --git a/net/minecraft/world/entity/ai/goal/BegGoal.java b/net/minecraft/world/entity/ai/goal/BegGoal.java -index 28ef40e8a645989ea181297069cf2bbe571f3082..d011e4735cb8fd65a39a6b7a66386375b12aca78 100644 +index 28ef40e8a645989ea181297069cf2bbe571f3082..1c128f60adf6a72bed0ca40b98dc579cdf9d0e21 100644 --- a/net/minecraft/world/entity/ai/goal/BegGoal.java +++ b/net/minecraft/world/entity/ai/goal/BegGoal.java @@ -27,8 +27,43 @@ public class BegGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.LOOK)); } -+ // Leaf start - Async Target Finding ++ // Leaf start - Async target finding + protected boolean poll() { + if (!(this.wolf.getGoalCtx().result() instanceof Player target)) return false; + if (target == null) return false; + ServerLevel serverLevel = getServerLevel(this.wolf); -+ if (serverLevel == null || !target.isAlive() || !playerHoldingInteresting(target)) return false; ++ if (serverLevel == null || !target.isAlive() || !playerHoldingInteresting0(target)) return false; + this.player = target; + return true; + } @@ -399,17 +399,17 @@ index 28ef40e8a645989ea181297069cf2bbe571f3082..d011e4735cb8fd65a39a6b7a66386375 + final TargetingConditions begTargeting = this.begTargeting; + ctx.wake = () -> { + var player = serverLevel.getNearestPlayer(begTargeting, wolf); -+ if (player != null && playerHoldingInteresting(player)) { ++ if (player != null && playerHoldingInteresting0(player)) { + return player; + } + return null; + }; + } -+ // Leaf end - Async Target Finding ++ // Leaf end - Async target finding + @Override public boolean canUse() { -+ // Leaf start - Async Target Finding ++ // Leaf start - Async target finding + if (poll()) { + return true; + } @@ -417,23 +417,28 @@ index 28ef40e8a645989ea181297069cf2bbe571f3082..d011e4735cb8fd65a39a6b7a66386375 + findTargetAsync(); + return false; + } -+ // Leaf end - Async Target Finding ++ // Leaf end - Async target finding this.player = this.level.getNearestPlayer(this.begTargeting, this.wolf); return this.player != null && this.playerHoldingInteresting(this.player); } -@@ -59,10 +94,10 @@ public class BegGoal extends Goal { - this.lookTime--; - } +@@ -69,4 +104,17 @@ public class BegGoal extends Goal { -- private boolean playerHoldingInteresting(Player player) { -+ private static boolean playerHoldingInteresting(Player player) { // Leaf start - Async Target Finding - static - for (InteractionHand interactionHand : InteractionHand.values()) { - ItemStack itemInHand = player.getItemInHand(interactionHand); -- if (itemInHand.is(Items.BONE) || this.wolf.isFood(itemInHand)) { -+ if (itemInHand.is(Items.BONE) || itemInHand.is(net.minecraft.tags.ItemTags.WOLF_FOOD)) { // Leaf end - Async Target Finding - return true; - } - } + return false; + } ++ ++ // Leaf start - Async target finding - static impl ++ private static boolean playerHoldingInteresting0(Player player) { ++ for (InteractionHand interactionHand : InteractionHand.values()) { ++ ItemStack itemInHand = player.getItemInHand(interactionHand); ++ if (itemInHand.is(Items.BONE) || itemInHand.is(net.minecraft.tags.ItemTags.WOLF_FOOD)) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ // Leaf end - Async target finding - static impl + } diff --git a/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java b/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java index 4c46cd105cde3cbcde65a02ff691c3a8edd56c76..b3205c9f35687bc37124876198ec2d657ccaa96c 100644 --- a/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java @@ -669,7 +674,7 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..0a41797fd7beddce0b93d42bac6e0270 } else { this.parent = animal; diff --git a/net/minecraft/world/entity/ai/goal/GoalSelector.java b/net/minecraft/world/entity/ai/goal/GoalSelector.java -index e82e32407cec6109b9c3b0106295217f4a3f4aa2..f522125d19e31ec3fd3d38d1fdb307ba25e300a8 100644 +index e82e32407cec6109b9c3b0106295217f4a3f4aa2..852393af4013178562940f6a0d07e5364758ec21 100644 --- a/net/minecraft/world/entity/ai/goal/GoalSelector.java +++ b/net/minecraft/world/entity/ai/goal/GoalSelector.java @@ -26,13 +26,23 @@ public class GoalSelector { @@ -718,7 +723,7 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..f522125d19e31ec3fd3d38d1fdb307ba flagIterator ^= ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit(flagIterator); // Paper end - Perf: optimize goal types if (!flag.getOrDefault(flag1, NO_GOAL).canBeReplacedBy(goal)) { -@@ -85,7 +96,131 @@ public class GoalSelector { +@@ -85,7 +96,136 @@ public class GoalSelector { return true; } @@ -743,7 +748,12 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..f522125d19e31ec3fd3d38d1fdb307ba + ctx.state = true; + } + -+ this.lockedFlags.entrySet().removeIf(entry -> !entry.getValue().isRunning()); ++ for (Goal.Flag flag : GOAL_FLAG_VALUES) { ++ WrappedGoal goal = this.lockedFlags.get(flag); ++ if (goal != null && !goal.isRunning()) { ++ this.lockedFlags.remove(flag); ++ } ++ } + + ctxIndex = 0; + ctx.state = true; @@ -791,7 +801,7 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..f522125d19e31ec3fd3d38d1fdb307ba + if (!ctx.state) { + switch (goal.getGoal()) { + case net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal t -> t.poll(); -+ case net.minecraft.world.entity.ai.goal.target.ResetUniversalAngerTargetGoal t -> t.poll(); ++ case net.minecraft.world.entity.ai.goal.target.ResetUniversalAngerTargetGoal t -> t.poll(); + default -> {} + } + } @@ -850,7 +860,7 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..f522125d19e31ec3fd3d38d1fdb307ba for (WrappedGoal wrappedGoal : this.availableGoals) { if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.goalTypes) || !wrappedGoal.canContinueToUse())) { // Paper - Perf: optimize goal types by removing streams wrappedGoal.stop(); -@@ -116,6 +251,24 @@ public class GoalSelector { +@@ -116,6 +256,24 @@ public class GoalSelector { } public void tickRunningGoals(boolean tickAllRunning) { @@ -863,7 +873,7 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..f522125d19e31ec3fd3d38d1fdb307ba + } + ctxState = tickAllRunning ? 2 : 3; + } else { -+ for (WrappedGoal wrappedGoal : this.ctxGoals) { ++ for (WrappedGoal wrappedGoal : java.util.Objects.requireNonNull(this.ctxGoals)) { + if (wrappedGoal.isRunning() && (tickAllRunning || wrappedGoal.requiresUpdateEveryTick())) { + wrappedGoal.tick(); + } @@ -1390,10 +1400,10 @@ index c67a88c9c77ece7c85ffb169ac96da4f28291228..93bf51c9299354be2a581618bd31d2c4 + // Leaf end - Async search block } diff --git a/net/minecraft/world/entity/ai/goal/TemptGoal.java b/net/minecraft/world/entity/ai/goal/TemptGoal.java -index f88f618d34fb343b31de3af1a875d6633703df71..754c379b42cf65c1d2278b474cdfbe50e9e62b34 100644 +index f88f618d34fb343b31de3af1a875d6633703df71..22656c9ef08a65e23cabdd9f6af84f3d2279b075 100644 --- a/net/minecraft/world/entity/ai/goal/TemptGoal.java +++ b/net/minecraft/world/entity/ai/goal/TemptGoal.java -@@ -36,12 +36,51 @@ public class TemptGoal extends Goal { +@@ -36,14 +36,43 @@ public class TemptGoal extends Goal { this.targetingConditions = TEMPT_TARGETING.copy().selector((entity, level) -> this.shouldFollow(entity)); } @@ -1423,28 +1433,22 @@ index f88f618d34fb343b31de3af1a875d6633703df71..754c379b42cf65c1d2278b474cdfbe50 this.calmDown--; return false; } else { +- this.player = getServerLevel(this.mob) +- .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); + // Leaf start - Async Tempt Finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { -+ if (poll()) { -+ if (this.player != null) { -+ org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT); -+ if (event.isCancelled()) { -+ return false; -+ } -+ this.player = (event.getTarget() == null) ? null : ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle(); -+ } -+ if (this.player != null) { -+ return true; -+ } -+ } else { ++ if (!poll()) { + getNearestPlayerAsync(); + return false; + } ++ } else { ++ this.player = getServerLevel(this.mob) ++ .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); + } + // Leaf end - Async Tempt Finding - this.player = getServerLevel(this.mob) - .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); // CraftBukkit start + if (this.player != null) { + org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT); diff --git a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..c9750ad322ddaa9c457f0e652d87c7abf8559358 100644 --- a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java From a0457c6ae339963e2d33e9dbfabccfa22f6fa7b1 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Wed, 28 May 2025 19:19:05 +0900 Subject: [PATCH 17/25] reduce tickFluid FlowingFluid#getSlopeDistance allocation --- .../0139-Use-BFS-on-getSlopeDistance.patch | 83 +++++++++++++++---- .../features/0153-Async-target-finding.patch | 6 +- .../features/0180-Paw-optimization.patch | 6 +- 3 files changed, 73 insertions(+), 22 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0139-Use-BFS-on-getSlopeDistance.patch b/leaf-server/minecraft-patches/features/0139-Use-BFS-on-getSlopeDistance.patch index bdefc5ba..33069a99 100644 --- a/leaf-server/minecraft-patches/features/0139-Use-BFS-on-getSlopeDistance.patch +++ b/leaf-server/minecraft-patches/features/0139-Use-BFS-on-getSlopeDistance.patch @@ -8,11 +8,26 @@ Paper: ~75ms Leaf: ~48ms (-36%) This should help drastically on the farms that use actively changing fluids. +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index d21ae1a6ce307c186bc7e218b947dd1879d93b00..7f823e7f282e52b7cf918b117a5059ab3f62aef9 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -1314,6 +1314,10 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.emptyTime = 0; + } + ++ // Leaf start - Use BFS on getSlopeDistance ++ public it.unimi.dsi.fastutil.longs.LongSet slopeDistanceCacheVisited = new it.unimi.dsi.fastutil.longs.LongOpenHashSet(512); ++ public net.minecraft.world.level.material.FlowingFluid.SlopeDistanceNodeDeque slopeDistanceCacheQueue = new net.minecraft.world.level.material.FlowingFluid.SlopeDistanceNodeDeque(); ++ // Leaf end - Use BFS on getSlopeDistance + private void tickFluid(BlockPos pos, Fluid fluid) { + BlockState blockState = this.getBlockState(pos); + FluidState fluidState = blockState.getFluidState(); diff --git a/net/minecraft/world/level/material/FlowingFluid.java b/net/minecraft/world/level/material/FlowingFluid.java -index 4c2c2efd5380ff1fa5ad7553b51babae20f516ae..f6bc70685e846e9114f477dfd8aceca3b910a09f 100644 +index 4c2c2efd5380ff1fa5ad7553b51babae20f516ae..cf8fd5df3472c3212fd3cf9536761c998aff26d4 100644 --- a/net/minecraft/world/level/material/FlowingFluid.java +++ b/net/minecraft/world/level/material/FlowingFluid.java -@@ -341,32 +341,81 @@ public abstract class FlowingFluid extends Fluid { +@@ -341,32 +341,117 @@ public abstract class FlowingFluid extends Fluid { protected void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state, BlockPos source) { beforeDestroyingBlock(level, pos, state); } // Paper - Add BlockBreakBlockEvent protected abstract void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state); @@ -20,13 +35,14 @@ index 4c2c2efd5380ff1fa5ad7553b51babae20f516ae..f6bc70685e846e9114f477dfd8aceca3 - 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); -+ java.util.Queue queue = new java.util.ArrayDeque<>(256); ++ it.unimi.dsi.fastutil.longs.LongSet visited = ((ServerLevel) level).slopeDistanceCacheVisited; ++ SlopeDistanceNodeDeque queue = ((ServerLevel) level).slopeDistanceCacheQueue; ++ visited.clear(); + + for (Direction dir : Direction.Plane.HORIZONTAL) { + if (dir == excludedDirection) continue; + -+ BlockPos neighborPos = startPos.relative(dir); ++ BlockPos neighborPos = startPos.relative(dir); // immutable + BlockState neighborState = spreadContext.getBlockStateIfLoaded(neighborPos); + if (neighborState == null) continue; + @@ -72,7 +88,7 @@ index 4c2c2efd5380ff1fa5ad7553b51babae20f516ae..f6bc70685e846e9114f477dfd8aceca3 + for (Direction dir : Direction.Plane.HORIZONTAL) { + if (dir == current.excludedDir) continue; + -+ BlockPos nextPos = current.pos.relative(dir); ++ BlockPos nextPos = current.pos.relative(dir); // immutable + BlockState nextState = spreadContext.getBlockStateIfLoaded(nextPos); + if (nextState == null) continue; + @@ -96,18 +112,53 @@ index 4c2c2efd5380ff1fa5ad7553b51babae20f516ae..f6bc70685e846e9114f477dfd8aceca3 + return ((long) pos.getX() & 0xFFFFFFFFL) << 32 | ((long) pos.getZ() & 0xFFFFFFFFL) << 4 | (excludedDir.ordinal() & 0x0F); + } + -+ private static class SlopeDistanceNode { -+ final BlockPos pos; -+ final int depth; -+ final Direction excludedDir; -+ final BlockState state; ++ public static class SlopeDistanceNodeDeque { ++ private SlopeDistanceNode[] array; ++ private int length; ++ private int start; ++ private int end; + -+ SlopeDistanceNode(BlockPos pos, int depth, Direction excludedDir, BlockState state) { -+ this.pos = pos.immutable(); -+ this.depth = depth; -+ this.excludedDir = excludedDir; -+ this.state = state; ++ public SlopeDistanceNodeDeque() { ++ array = new SlopeDistanceNode[256]; ++ length = array.length; + } ++ ++ /* ++ private int size() { ++ int apparent = end - start; ++ return apparent >= 0 ? apparent : length + apparent; ++ } ++ */ ++ ++ private boolean isEmpty() { ++ return end == start || (end <= start && length == start - end); ++ } ++ ++ private SlopeDistanceNode poll() { ++ final SlopeDistanceNode t = array[start]; ++ if (++start == length) start = 0; ++ return t; ++ } ++ ++ private void add(final SlopeDistanceNode node) { ++ array[end++] = node; ++ if (end == length) end = 0; ++ if (end == start) resize(length, 2 * length); ++ } ++ ++ private void resize(final int size, final int newLength) { ++ final SlopeDistanceNode[] newArray = new SlopeDistanceNode[newLength]; ++ if (size != 0) { ++ System.arraycopy(array, start, newArray, 0, length - start); ++ System.arraycopy(array, 0, newArray, length - start, end); ++ } ++ start = 0; ++ end = size; ++ array = newArray; ++ length = newLength; ++ } ++ } ++ private record SlopeDistanceNode(BlockPos pos, int depth, Direction excludedDir, BlockState state) { } + // Leaf end - Use BFS on getSlopeDistance diff --git a/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch index c714bf57..d504636c 100644 --- a/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0153-Async-target-finding.patch @@ -149,7 +149,7 @@ index 33dd16a26edd2974f04d9a868d3e58e8e3060032..eb0589b203bcf72cd24bb37f2c448c23 // Gale start - Pufferfish - SIMD support diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 9af7dafe03812d96aa477584d4147a68c240ab21..e6fd46b8148e050c4807abf6c8a03e4747bc0da2 100644 +index 60d5539f2a94b19281bcd8bdc2044eae1225278d..e95cbbc2757ed7f8d8aa873cec6bd49269592dae 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -177,7 +177,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -220,7 +220,7 @@ index 9af7dafe03812d96aa477584d4147a68c240ab21..e6fd46b8148e050c4807abf6c8a03e47 } // Paper - rewrite chunk system -@@ -1329,6 +1354,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1333,6 +1358,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper end - rewrite chunk system @@ -228,7 +228,7 @@ index 9af7dafe03812d96aa477584d4147a68c240ab21..e6fd46b8148e050c4807abf6c8a03e47 } private void tickBlock(BlockPos pos, Block block) { -@@ -1345,6 +1371,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1349,6 +1375,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper end - rewrite chunk system diff --git a/leaf-server/minecraft-patches/features/0180-Paw-optimization.patch b/leaf-server/minecraft-patches/features/0180-Paw-optimization.patch index 5e1b9fb8..b193ab0d 100644 --- a/leaf-server/minecraft-patches/features/0180-Paw-optimization.patch +++ b/leaf-server/minecraft-patches/features/0180-Paw-optimization.patch @@ -94,10 +94,10 @@ index b1f1b596a597d559aa672a3cb46a03917ad746af..d61da0fbe7f6c181e4084ce60bfe7dab this.tickChunks(l, list); // Gale - Purpur - remove vanilla profiler } finally { diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index e6fd46b8148e050c4807abf6c8a03e4747bc0da2..0d8b71bbe5835187d5dfc1a301b6a01b33237bc1 100644 +index e95cbbc2757ed7f8d8aa873cec6bd49269592dae..3a5d2b16b0eccc8d6a742f70039b999a364d3516 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -1387,13 +1387,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1391,13 +1391,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // Paper end - log detailed entity tick information public void tickNonPassenger(Entity entity) { @@ -111,7 +111,7 @@ index e6fd46b8148e050c4807abf6c8a03e4747bc0da2..0d8b71bbe5835187d5dfc1a301b6a01b entity.setOldPosAndRot(); entity.tickCount++; entity.totalEntityAge++; // Paper - age-like counter for all entities -@@ -1406,13 +1400,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1410,13 +1404,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe for (Entity entity1 : entity.getPassengers()) { this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2 } From df1d62bed97518ebd21c66ac7bf3d8488a9173bf Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Thu, 29 May 2025 12:20:52 +0900 Subject: [PATCH 18/25] MultithreadedTracker add lock to AttributeMap and TrackedChunk --- .../features/0085-Multithreaded-Tracker.patch | 272 +++++++++++++++--- ...ecessary-calculations-if-player-is-n.patch | 6 +- .../0103-Lithium-equipment-tracking.patch | 8 +- .../features/0105-Cache-chunk-key.patch | 8 +- .../features/0119-Only-player-pushable.patch | 8 +- ...ptimize-addOrUpdateTransientModifier.patch | 4 +- .../features/0163-Protocol-Core.patch | 4 +- .../features/0176-Optimize-AttributeMap.patch | 34 ++- .../async/tracker/MultithreadedTracker.java | 41 +-- 9 files changed, 286 insertions(+), 99 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch b/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch index b0a5032c..b08d943c 100644 --- a/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch +++ b/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch @@ -23,6 +23,28 @@ for the case of some NPC plugins which using real entity type, e.g. Citizens. But it is still recommending to use those packet based, virtual entity based NPC plugins, e.g. ZNPC Plus, Adyeshach, Fancy NPC, etc. +diff --git a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java +index 1b8193587814225c2ef2c5d9e667436eb50ff6c5..4200d22606c6a3dbdf282792a4007a51df66963b 100644 +--- a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java ++++ b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java +@@ -60,7 +60,16 @@ public final class NearbyPlayers { + + private final ServerLevel world; + private final Reference2ReferenceOpenHashMap players = new Reference2ReferenceOpenHashMap<>(); +- private final Long2ReferenceOpenHashMap byChunk = new Long2ReferenceOpenHashMap<>(); ++ // Leaf start - Multithreaded tracker ++ private final it.unimi.dsi.fastutil.longs.Long2ReferenceMap byChunk; ++ { ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { ++ byChunk = it.unimi.dsi.fastutil.longs.Long2ReferenceMaps.synchronize(new Long2ReferenceOpenHashMap<>()); ++ } else { ++ byChunk = new Long2ReferenceOpenHashMap<>(); ++ } ++ } ++ // Leaf end - Multithreaded tracker + private final Long2ReferenceOpenHashMap>[] directByChunk = new Long2ReferenceOpenHashMap[TOTAL_MAP_TYPES]; + { + for (int i = 0; i < this.directByChunk.length; ++i) { diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java index dd2509996bfd08e8c3f9f2be042229eac6d7692d..a35e9fae8f8da0c42f0616c4f78dc396492673aa 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java @@ -37,7 +59,7 @@ index dd2509996bfd08e8c3f9f2be042229eac6d7692d..a35e9fae8f8da0c42f0616c4f78dc396 private static final byte CHUNK_TICKET_STAGE_NONE = 0; private static final byte CHUNK_TICKET_STAGE_LOADING = 1; diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java -index 5d9d233e3a568aa6297ed9c703fa450f98158602..24765ca23899b2eec049bf539c1f9eafc8b48d1b 100644 +index 5d9d233e3a568aa6297ed9c703fa450f98158602..47a7bf7c38600a2ad547bbd2b7fe632e96e9a139 100644 --- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java @@ -248,6 +248,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -70,14 +92,13 @@ index 5d9d233e3a568aa6297ed9c703fa450f98158602..24765ca23899b2eec049bf539c1f9eaf // Paper start - optimise entity tracker if (true) { this.newTrackerTick(); -@@ -1073,7 +1089,18 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1073,7 +1089,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider final Entity entity; private final int range; SectionPos lastSectionPos; - public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl + // Leaf start - Multithreaded tracker + public static final ServerPlayerConnection[] EMPTY_OBJECT_ARRAY = new ServerPlayerConnection[0]; -+ public final Object sync = new Object(); + public final Set seenBy = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled ? it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>()) : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl + private volatile ServerPlayerConnection[] seenByArray = EMPTY_OBJECT_ARRAY; + public ServerPlayerConnection[] seenBy() { @@ -90,7 +111,7 @@ index 5d9d233e3a568aa6297ed9c703fa450f98158602..24765ca23899b2eec049bf539c1f9eaf // Paper start - optimise entity tracker private long lastChunkUpdate = -1L; -@@ -1100,27 +1127,95 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1100,27 +1126,95 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.lastTrackedChunk = chunk; final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); @@ -191,7 +212,7 @@ index 5d9d233e3a568aa6297ed9c703fa450f98158602..24765ca23899b2eec049bf539c1f9eaf if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(conn.getPlayer())) { foundToRemove = true; break; -@@ -1131,12 +1226,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1131,12 +1225,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider return; } @@ -207,7 +228,7 @@ index 5d9d233e3a568aa6297ed9c703fa450f98158602..24765ca23899b2eec049bf539c1f9eaf } @Override -@@ -1146,10 +1242,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1146,10 +1241,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider if (this.seenBy.isEmpty()) { return; } @@ -221,7 +242,7 @@ index 5d9d233e3a568aa6297ed9c703fa450f98158602..24765ca23899b2eec049bf539c1f9eaf } @Override -@@ -1176,7 +1273,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1176,7 +1272,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void broadcast(Packet packet) { @@ -230,7 +251,7 @@ index 5d9d233e3a568aa6297ed9c703fa450f98158602..24765ca23899b2eec049bf539c1f9eaf serverPlayerConnection.send(packet); } } -@@ -1189,21 +1286,34 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1189,21 +1285,34 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void broadcastRemoved() { @@ -268,7 +289,7 @@ index 5d9d233e3a568aa6297ed9c703fa450f98158602..24765ca23899b2eec049bf539c1f9eaf // Paper start - remove allocation of Vec3D here // Vec3 vec3 = player.position().subtract(this.entity.position()); double vec3_dx = player.getX() - this.entity.getX(); -@@ -1231,6 +1341,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1231,6 +1340,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider // CraftBukkit end if (flag) { if (this.seenBy.add(player.connection)) { @@ -276,7 +297,7 @@ index 5d9d233e3a568aa6297ed9c703fa450f98158602..24765ca23899b2eec049bf539c1f9eaf // Paper start - entity tracking events if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) { this.serverEntity.addPairing(player); -@@ -1239,6 +1350,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1239,6 +1349,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.serverEntity.onPlayerAdd(); // Paper - fix desync when a player is added to the tracker } } else if (this.seenBy.remove(player.connection)) { @@ -298,18 +319,10 @@ index f106373ef3ac4a8685c2939c9e8361688a285913..51ae390c68e7a3aa193329cc3bc47ca6 public boolean visible = true; diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java -index 1dee20436fc29537319ee456756a8e8f7b6fe66a..1f3e030cea42a0c9e27425cb18c232f482ef8608 100644 +index 1dee20436fc29537319ee456756a8e8f7b6fe66a..bcd569e7d12d4f453c64bf12933a72c3ca362329 100644 --- a/net/minecraft/server/level/ServerEntity.java +++ b/net/minecraft/server/level/ServerEntity.java -@@ -70,6 +70,7 @@ public class ServerEntity { - private boolean wasOnGround; - @Nullable - private List> trackedDataValues; -+ public boolean wantSendDirtyEntityData = false; // Leaf - Multithreaded tracker - - // CraftBukkit start - private final Set trackedPlayers; -@@ -110,8 +111,16 @@ public class ServerEntity { +@@ -110,8 +110,16 @@ public class ServerEntity { .forEach( removedPassenger -> { if (removedPassenger instanceof ServerPlayer serverPlayer1) { @@ -328,7 +341,7 @@ index 1dee20436fc29537319ee456756a8e8f7b6fe66a..1f3e030cea42a0c9e27425cb18c232f4 } } ); -@@ -124,7 +133,7 @@ public class ServerEntity { +@@ -124,7 +132,7 @@ public class ServerEntity { MapId mapId = itemFrame.cachedMapId; // Paper - Perf: Cache map ids on item frames MapItemSavedData savedData = MapItem.getSavedData(mapId, this.level); if (savedData != null) { @@ -337,19 +350,38 @@ index 1dee20436fc29537319ee456756a8e8f7b6fe66a..1f3e030cea42a0c9e27425cb18c232f4 final ServerPlayer serverPlayer = connection.getPlayer(); // Paper savedData.tickCarriedBy(serverPlayer, item); Packet updatePacket = savedData.getUpdatePacket(mapId, serverPlayer); -@@ -425,6 +434,12 @@ public class ServerEntity { - } +@@ -433,15 +441,30 @@ public class ServerEntity { + } - public void sendDirtyEntityData() { -+ // Leaf start - Multithreaded tracker -+ if (Thread.currentThread() instanceof org.dreeam.leaf.async.tracker.MultithreadedTracker.MultithreadedTrackerThread) { -+ wantSendDirtyEntityData = true; -+ return; -+ } -+ // Leaf end - Multithreaded tracker - SynchedEntityData entityData = this.entity.getEntityData(); - List> list = entityData.packDirty(); - if (list != null) { + if (this.entity instanceof LivingEntity) { +- Set attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync(); ++ // Leaf start - Multithreaded tracker ++ var attributeMap = ((LivingEntity)this.entity).getAttributes(); ++ Set attributesToSync = attributeMap.getAttributesToSync(); + if (!attributesToSync.isEmpty()) { ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { ++ synchronized (attributeMap) { ++ // CraftBukkit start - Send scaled max health ++ if (this.entity instanceof ServerPlayer serverPlayer) { ++ serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false); ++ } ++ // CraftBukkit end ++ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); ++ ++ } ++ } else { + // CraftBukkit start - Send scaled max health + if (this.entity instanceof ServerPlayer serverPlayer) { + serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false); + } + // CraftBukkit end + this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); ++ } + } ++ // Leaf end - Multithreaded tracker + + attributesToSync.clear(); + } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java index 275b640f4536366152f59acf071dd4eba15696c8..a669a59a42f814480879a52d2da5e04c636720de 100644 --- a/net/minecraft/server/level/ServerLevel.java @@ -385,6 +417,180 @@ index 327b3bc89920c4ab02c1126dc63bca05ce3abefe..1415043bee5fbbfcf9dab9184a9418d5 // Paper start - Prevent teleporting dead entities if (this.player.isRemoved()) { LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java +index d502325d693539842fd6f5485365e0e9b786b7aa..324f8952a921a3897f4ff48145f0f8645c690318 100644 +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -1311,13 +1311,26 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + private void refreshDirtyAttributes() { +- Set attributesToUpdate = this.getAttributes().getAttributesToUpdate(); ++ // Leaf start - Multithreaded tracker ++ var attributeMap = this.getAttributes(); ++ Set attributesToUpdate = attributeMap.getAttributesToUpdate(); ++ ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { ++ synchronized (attributeMap) { ++ for (AttributeInstance attributeInstance : attributesToUpdate) { ++ this.onAttributeUpdated(attributeInstance.getAttribute()); ++ } + ++ attributesToUpdate.clear(); ++ } ++ } else { + for (AttributeInstance attributeInstance : attributesToUpdate) { + this.onAttributeUpdated(attributeInstance.getAttribute()); + } + + attributesToUpdate.clear(); ++ } ++ // Leaf end - Multithreaded tracker + } + + protected void onAttributeUpdated(Holder attribute) { +diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +index 8013594bb4844e7a8abf28123958e7f632d39341..93b375f39f10568f6b222607890a9ce67db0e9bb 100644 +--- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +@@ -24,8 +24,24 @@ public class AttributeInstance { + private final Map> modifiersByOperation = Maps.newEnumMap( + AttributeModifier.Operation.class + ); +- private final Map modifierById = new Object2ObjectArrayMap<>(); +- private final Map permanentModifiers = new Object2ObjectArrayMap<>(); ++ // Leaf start - Multithreaded tracker ++ private final Map modifierById; ++ { ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { ++ modifierById = it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this); ++ } else { ++ modifierById = new Object2ObjectArrayMap<>(); ++ } ++ } ++ private final Map permanentModifiers; ++ { ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { ++ permanentModifiers = it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this); ++ } else { ++ permanentModifiers = new Object2ObjectArrayMap<>(); ++ } ++ } ++ // Leaf end - Multithreaded tracker + private double baseValue; + private boolean dirty = true; + private double cachedValue; +@@ -54,7 +70,13 @@ public class AttributeInstance { + + @VisibleForTesting + Map getModifiers(AttributeModifier.Operation operation) { +- return this.modifiersByOperation.computeIfAbsent(operation, operation1 -> new Object2ObjectOpenHashMap<>()); ++ // Leaf start - Multithreaded tracker ++ return this.modifiersByOperation.computeIfAbsent(operation, operation1 -> { ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) ++ return it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this); ++ else return new Object2ObjectArrayMap<>(); ++ }); ++ // Leaf end - Multithreaded tracker + } + + public Set getModifiers() { +@@ -174,6 +196,13 @@ public class AttributeInstance { + } + + public void replaceFrom(AttributeInstance instance) { ++ // Leaf start - Multithreaded tracker ++ synchronized (instance) { ++ this.replaceFrom0(instance); ++ } ++ } ++ public void replaceFrom0(AttributeInstance instance) { ++ // Leaf end - Multithreaded tracker + this.baseValue = instance.baseValue; + this.modifierById.clear(); + this.modifierById.putAll(instance.modifierById); +@@ -195,9 +224,19 @@ public class AttributeInstance { + if (!this.permanentModifiers.isEmpty()) { + ListTag listTag = new ListTag(); + +- for (AttributeModifier attributeModifier : this.permanentModifiers.values()) { +- listTag.add(attributeModifier.save()); ++ // Leaf start - Multithreaded tracker ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { ++ synchronized (this) { ++ for (AttributeModifier attributeModifier : this.permanentModifiers.values()) { ++ listTag.add(attributeModifier.save()); ++ } ++ } ++ } else { ++ for (AttributeModifier attributeModifier : this.permanentModifiers.values()) { ++ listTag.add(attributeModifier.save()); ++ } + } ++ // Leaf end - Multithreaded tracker + + compoundTag.put("modifiers", listTag); + } +diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java +index 89f4c5b2d61e27acd48063f9f24ce9ea91898b8b..c0a09b615e9b6c4ec72b8b77a78e7da374d4498b 100644 +--- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java +@@ -21,8 +21,19 @@ public class AttributeMap { + private static final Logger LOGGER = LogUtils.getLogger(); + // Gale start - Lithium - replace AI attributes with optimized collections + private final Map, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); +- private final Set attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); +- private final Set attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); ++ // Leaf start - Multithreaded tracker ++ private final Set attributesToSync; ++ private final Set attributesToUpdate; ++ { ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { ++ attributesToSync = it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0), this); ++ attributesToUpdate = it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0), this); ++ } else { ++ attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); ++ attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); ++ } ++ } ++ // Leaf end - Multithreaded tracker + // Gale end - Lithium - replace AI attributes with optimized collections + private final AttributeSupplier supplier; + private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations +@@ -60,7 +71,13 @@ public class AttributeMap { + + @Nullable + public AttributeInstance getInstance(Holder attribute) { +- return this.attributes.computeIfAbsent(attribute, this.createInstance); // Gale - Airplane - reduce entity allocations - cache lambda, as for some reason java allocates it anyways ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { ++ synchronized (this) { ++ return this.attributes.computeIfAbsent(attribute, holder -> this.supplier.createInstance(this::onAttributeModified, (Holder) holder)); ++ } ++ } else { ++ return this.attributes.computeIfAbsent(attribute, holder -> this.supplier.createInstance(this::onAttributeModified, (Holder) holder)); ++ } + } + + public boolean hasAttribute(Holder attribute) { +@@ -176,8 +193,17 @@ public class AttributeMap { + + // Paper - start - living entity allow attribute registration + public void registerAttribute(Holder attributeBase) { ++ // Leaf start - Multithreaded tracker + AttributeInstance attributeModifiable = new AttributeInstance(attributeBase, AttributeInstance::getAttribute); +- attributes.put(attributeBase, attributeModifiable); ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { ++ synchronized (this) { ++ attributes.put(attributeBase, attributeModifiable); ++ ++ } ++ } else { ++ attributes.put(attributeBase, attributeModifiable); ++ } ++ // Leaf end - Multithreaded tracker + } + // Paper - end - living entity allow attribute registration + diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java index c96f458994818392857642282ec3d492124885da..d345afd14ef6fe2f0a584df5dfa080fd7ab3f47e 100644 --- a/net/minecraft/world/entity/item/PrimedTnt.java diff --git a/leaf-server/minecraft-patches/features/0097-Lithium-Skip-unnecessary-calculations-if-player-is-n.patch b/leaf-server/minecraft-patches/features/0097-Lithium-Skip-unnecessary-calculations-if-player-is-n.patch index e058cc2f..23754ae5 100644 --- a/leaf-server/minecraft-patches/features/0097-Lithium-Skip-unnecessary-calculations-if-player-is-n.patch +++ b/leaf-server/minecraft-patches/features/0097-Lithium-Skip-unnecessary-calculations-if-player-is-n.patch @@ -12,10 +12,10 @@ As part of: Lithium (https://github.com/CaffeineMC/lithium-fabric) Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java -index 316242d60db43494300a29b7d0945d0d76ac9987..0138bd4d95a592bfa5ccbb33fa6c1201f289fd2a 100644 +index 324f8952a921a3897f4ff48145f0f8645c690318..3e3fdcff3725841c8b04047a91733c8b98187e0b 100644 --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java -@@ -2739,6 +2739,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -2752,6 +2752,7 @@ public abstract class LivingEntity extends Entity implements Attackable { } protected void updateSwingTime() { @@ -23,7 +23,7 @@ index 316242d60db43494300a29b7d0945d0d76ac9987..0138bd4d95a592bfa5ccbb33fa6c1201 int currentSwingDuration = this.getCurrentSwingDuration(); if (this.swinging) { this.swingTime++; -@@ -3690,6 +3691,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -3703,6 +3704,7 @@ public abstract class LivingEntity extends Entity implements Attackable { protected void updateFallFlying() { this.checkSlowFallDistance(); if (!this.level().isClientSide) { diff --git a/leaf-server/minecraft-patches/features/0103-Lithium-equipment-tracking.patch b/leaf-server/minecraft-patches/features/0103-Lithium-equipment-tracking.patch index 9c174967..ba404481 100644 --- a/leaf-server/minecraft-patches/features/0103-Lithium-equipment-tracking.patch +++ b/leaf-server/minecraft-patches/features/0103-Lithium-equipment-tracking.patch @@ -76,7 +76,7 @@ index a8c6549f772208cd543607224fef2c2389b14f24..709631db548a16a969a373e26ebbcd69 public boolean equals(Object other) { return this == other diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java -index 0138bd4d95a592bfa5ccbb33fa6c1201f289fd2a..00233a7066d751821566b43993e8c45e7dad95d0 100644 +index 3e3fdcff3725841c8b04047a91733c8b98187e0b..ffae6778b471628639c41b1d5f3af08b6b72a86c 100644 --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java @@ -159,7 +159,7 @@ import org.bukkit.event.entity.EntityTeleportEvent; @@ -116,7 +116,7 @@ index 0138bd4d95a592bfa5ccbb33fa6c1201f289fd2a..00233a7066d751821566b43993e8c45e Equippable equippable = newItem.get(DataComponents.EQUIPPABLE); if (!this.isSilent() && equippable != null && slot == equippable.slot() && !silent) { // CraftBukkit this.level() -@@ -3355,6 +3360,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -3368,6 +3373,7 @@ public abstract class LivingEntity extends Entity implements Attackable { public void detectEquipmentUpdatesPublic() { // CraftBukkit Map map = this.collectEquipmentChanges(); if (map != null) { @@ -124,7 +124,7 @@ index 0138bd4d95a592bfa5ccbb33fa6c1201f289fd2a..00233a7066d751821566b43993e8c45e this.handleHandSwap(map); if (!map.isEmpty()) { this.handleEquipmentChanges(map); -@@ -3364,6 +3370,10 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -3377,6 +3383,10 @@ public abstract class LivingEntity extends Entity implements Attackable { @Nullable private Map collectEquipmentChanges() { @@ -135,7 +135,7 @@ index 0138bd4d95a592bfa5ccbb33fa6c1201f289fd2a..00233a7066d751821566b43993e8c45e Map map = null; // Paper start - EntityEquipmentChangedEvent record EquipmentChangeImpl(org.bukkit.inventory.ItemStack oldItem, org.bukkit.inventory.ItemStack newItem) implements io.papermc.paper.event.entity.EntityEquipmentChangedEvent.EquipmentChange { -@@ -4723,6 +4733,81 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -4736,6 +4746,81 @@ public abstract class LivingEntity extends Entity implements Attackable { return this.lastHurtByPlayerTime; } diff --git a/leaf-server/minecraft-patches/features/0105-Cache-chunk-key.patch b/leaf-server/minecraft-patches/features/0105-Cache-chunk-key.patch index 07654988..38406bc0 100644 --- a/leaf-server/minecraft-patches/features/0105-Cache-chunk-key.patch +++ b/leaf-server/minecraft-patches/features/0105-Cache-chunk-key.patch @@ -9,10 +9,10 @@ This patch didn't cahce SectionPos or BlockPos to chunkKey, since it needs to co TODO: Cache block pos and section pos, whether need? diff --git a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -index 1b8193587814225c2ef2c5d9e667436eb50ff6c5..288a3eb57f3431dd624ad8a4b08684563abbc5ad 100644 +index 4200d22606c6a3dbdf282792a4007a51df66963b..4b258f048c73107d0d050a9aa4b4a39788145b17 100644 --- a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java +++ b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -@@ -127,7 +127,7 @@ public final class NearbyPlayers { +@@ -136,7 +136,7 @@ public final class NearbyPlayers { } public TrackedChunk getChunk(final ChunkPos pos) { @@ -21,7 +21,7 @@ index 1b8193587814225c2ef2c5d9e667436eb50ff6c5..288a3eb57f3431dd624ad8a4b0868456 } public TrackedChunk getChunk(final BlockPos pos) { -@@ -143,7 +143,7 @@ public final class NearbyPlayers { +@@ -152,7 +152,7 @@ public final class NearbyPlayers { } public ReferenceList getPlayers(final ChunkPos pos, final NearbyMapType type) { @@ -84,7 +84,7 @@ index 571db5f9bf94745a8afe2cd313e593fb15db5e37..1487b7d8be435b3fbad2aabd05796965 valueInMap = new ServerChunkTasks( keyInMap, ServerLightQueue.this.lightInterface, ServerLightQueue.this, priority diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 49cbdf014d0626b36eb4c451b6de09508822b7fd..a9e7424bb55266c5e04c56dcf598ce7d149eeb21 100644 +index a669a59a42f814480879a52d2da5e04c636720de..61afe93ff7f6f6ac3967e948bf39b0ab559e2808 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -508,7 +508,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe diff --git a/leaf-server/minecraft-patches/features/0119-Only-player-pushable.patch b/leaf-server/minecraft-patches/features/0119-Only-player-pushable.patch index 6b8b3ffa..df97992b 100644 --- a/leaf-server/minecraft-patches/features/0119-Only-player-pushable.patch +++ b/leaf-server/minecraft-patches/features/0119-Only-player-pushable.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Only player pushable Useful for extreme cases like massive entities collide together in a small area diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java -index 00233a7066d751821566b43993e8c45e7dad95d0..03c9edad5c2f5e902b7a766c6d0be61bf3c263ae 100644 +index ffae6778b471628639c41b1d5f3af08b6b72a86c..32039b50946b23e4f55498aa09ea33cfa77b517c 100644 --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java -@@ -3631,7 +3631,7 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf +@@ -3644,7 +3644,7 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf this.checkAutoSpinAttack(boundingBox, this.getBoundingBox()); } @@ -18,7 +18,7 @@ index 00233a7066d751821566b43993e8c45e7dad95d0..03c9edad5c2f5e902b7a766c6d0be61b // Paper start - Add EntityMoveEvent // Purpur start - Ridables if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { -@@ -3769,7 +3769,14 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf +@@ -3782,7 +3782,14 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf return; } // Paper end - don't run getEntities if we're not going to use its result @@ -34,7 +34,7 @@ index 00233a7066d751821566b43993e8c45e7dad95d0..03c9edad5c2f5e902b7a766c6d0be61b if (!entities.isEmpty()) { // Paper - don't run getEntities if we're not going to use its result; moved up if (_int > 0 && entities.size() > _int - 1 && this.random.nextInt(4) == 0) { -@@ -3802,6 +3809,44 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf +@@ -3815,6 +3822,44 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf } } diff --git a/leaf-server/minecraft-patches/features/0143-Optimize-addOrUpdateTransientModifier.patch b/leaf-server/minecraft-patches/features/0143-Optimize-addOrUpdateTransientModifier.patch index 087595bb..7bee8026 100644 --- a/leaf-server/minecraft-patches/features/0143-Optimize-addOrUpdateTransientModifier.patch +++ b/leaf-server/minecraft-patches/features/0143-Optimize-addOrUpdateTransientModifier.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Optimize addOrUpdateTransientModifier diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -index 8013594bb4844e7a8abf28123958e7f632d39341..7505485c8965e5492a9d68288596178cfe0971ee 100644 +index 93b375f39f10568f6b222607890a9ce67db0e9bb..69ba880e17e5cff3b2260f539683ca565f34e8dd 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -@@ -85,8 +85,13 @@ public class AttributeInstance { +@@ -107,8 +107,13 @@ public class AttributeInstance { } public void addOrUpdateTransientModifier(AttributeModifier modifier) { diff --git a/leaf-server/minecraft-patches/features/0163-Protocol-Core.patch b/leaf-server/minecraft-patches/features/0163-Protocol-Core.patch index 59c3cd8d..67e20d21 100644 --- a/leaf-server/minecraft-patches/features/0163-Protocol-Core.patch +++ b/leaf-server/minecraft-patches/features/0163-Protocol-Core.patch @@ -34,10 +34,10 @@ index 98af1ad020a003db66d7319f33d43deec315aec5..9669036e6b7f1830888e48c99acb01d4 for (int i = 0; i < this.tickables.size(); i++) { this.tickables.get(i).run(); diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java -index e67d87b3043b381d27f75f37e3b7f922e18dcc2d..d64434d808d164ab1201f244058e2964860a590c 100644 +index bcd569e7d12d4f453c64bf12933a72c3ca362329..b48032bc878e11518d63c128edeef6bf3770f7d1 100644 --- a/net/minecraft/server/level/ServerEntity.java +++ b/net/minecraft/server/level/ServerEntity.java -@@ -284,6 +284,7 @@ public class ServerEntity { +@@ -283,6 +283,7 @@ public class ServerEntity { this.entity.hurtMarked = false; this.broadcastAndSend(new ClientboundSetEntityMotionPacket(this.entity)); } diff --git a/leaf-server/minecraft-patches/features/0176-Optimize-AttributeMap.patch b/leaf-server/minecraft-patches/features/0176-Optimize-AttributeMap.patch index da4562d9..b22d676f 100644 --- a/leaf-server/minecraft-patches/features/0176-Optimize-AttributeMap.patch +++ b/leaf-server/minecraft-patches/features/0176-Optimize-AttributeMap.patch @@ -25,17 +25,20 @@ index f8419dde44ebc7324e783f8bee42132d5ec973c3..406767c60ec1a324faaf5d3658b16164 public double getDefaultValue() { diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java -index 89f4c5b2d61e27acd48063f9f24ce9ea91898b8b..51a5a3a804a1cbb0e1d23be432043552b102d837 100644 +index c0a09b615e9b6c4ec72b8b77a78e7da374d4498b..a550e6bd9dffa9a46f0d7967c9d73ff5cbfbaa0a 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java -@@ -20,12 +20,12 @@ import org.slf4j.Logger; +@@ -20,7 +20,7 @@ import org.slf4j.Logger; public class AttributeMap { private static final Logger LOGGER = LogUtils.getLogger(); // Gale start - Lithium - replace AI attributes with optimized collections - private final Map, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); + private final Map, AttributeInstance> attributes = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(); // Leaf - Optimize AttributeMap - private final Set attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); - private final Set attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); + // Leaf start - Multithreaded tracker + private final Set attributesToSync; + private final Set attributesToUpdate; +@@ -36,7 +36,7 @@ public class AttributeMap { + // Leaf end - Multithreaded tracker // Gale end - Lithium - replace AI attributes with optimized collections private final AttributeSupplier supplier; - private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations @@ -43,7 +46,7 @@ index 89f4c5b2d61e27acd48063f9f24ce9ea91898b8b..51a5a3a804a1cbb0e1d23be432043552 private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables public AttributeMap(AttributeSupplier supplier) { -@@ -36,7 +36,7 @@ public class AttributeMap { +@@ -47,7 +47,7 @@ public class AttributeMap { this.entity = entity; // Purpur end - Ridables this.supplier = defaultAttributes; @@ -52,20 +55,31 @@ index 89f4c5b2d61e27acd48063f9f24ce9ea91898b8b..51a5a3a804a1cbb0e1d23be432043552 } private void onAttributeModified(AttributeInstance instance) { -@@ -60,7 +60,17 @@ public class AttributeMap { +@@ -71,13 +71,24 @@ public class AttributeMap { @Nullable public AttributeInstance getInstance(Holder attribute) { -- return this.attributes.computeIfAbsent(attribute, this.createInstance); // Gale - Airplane - reduce entity allocations - cache lambda, as for some reason java allocates it anyways +- if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { +- synchronized (this) { +- return this.attributes.computeIfAbsent(attribute, holder -> this.supplier.createInstance(this::onAttributeModified, (Holder) holder)); + // Leaf start - Optimize AttributeMap + AttributeInstance v; + if ((v = this.attributes.get(attribute)) == null) { + AttributeInstance newValue; -+ if ((newValue = this.supplier.createInstance(this::onAttributeModified, attribute)) != null) { ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { ++ synchronized (this) { ++ if ((newValue = this.supplier.createInstance(this::onAttributeModified, attribute)) != null) { ++ this.attributes.put(attribute, newValue); ++ return newValue; ++ } ++ } ++ } else if ((newValue = this.supplier.createInstance(this::onAttributeModified, attribute)) != null) { + this.attributes.put(attribute, newValue); + return newValue; -+ } -+ } + } +- } else { +- return this.attributes.computeIfAbsent(attribute, holder -> this.supplier.createInstance(this::onAttributeModified, (Holder) holder)); + } + return v; + // Leaf end - Optimize AttributeMap } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java b/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java index e1ce6caa..7ef71a0f 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java @@ -32,15 +32,6 @@ public class MultithreadedTracker { private static long lastWarnMillis = System.currentTimeMillis(); private static ThreadPoolExecutor TRACKER_EXECUTOR = null; - private record SendChanges(ServerEntity[] entities, int size) implements Runnable { - @Override - public void run() { - for (int i = 0; i < size; i++) { - entities[i].sendDirtyEntityData(); - } - } - } - private MultithreadedTracker() { } @@ -80,7 +71,6 @@ public class MultithreadedTracker { // Move tracking to off-main TRACKER_EXECUTOR.execute(() -> { - ReferenceArrayList sendDirty = ReferenceArrayList.wrap(new ServerEntity[0]); for (final Entity entity : trackerEntitiesRaw) { if (entity == null) continue; @@ -88,19 +78,12 @@ public class MultithreadedTracker { if (tracker == null) continue; - // Don't Parallel Tick Tracker of Entity - synchronized (tracker.sync) { - tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); + synchronized (tracker) { + var trackedChunk = nearbyPlayers.getChunk(entity.chunkPosition()); + tracker.moonrise$tick(trackedChunk); tracker.serverEntity.sendChanges(); - if (tracker.serverEntity.wantSendDirtyEntityData) { - tracker.serverEntity.wantSendDirtyEntityData = false; - sendDirty.add(tracker.serverEntity); - } } } - if (!sendDirty.isEmpty()) { - level.getServer().execute(new SendChanges(sendDirty.elements(), sendDirty.size())); - } }); } @@ -121,7 +104,7 @@ public class MultithreadedTracker { if (tracker == null) continue; - synchronized (tracker.sync) { + synchronized (tracker) { tickTask[index] = tracker.leafTickCompact(nearbyPlayers.getChunk(entity.chunkPosition())); sendChangesTasks[index] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array } @@ -140,22 +123,6 @@ public class MultithreadedTracker { sendChanges.run(); } - - ReferenceArrayList sendDirty = ReferenceArrayList.wrap(new ServerEntity[0]);; - for (final Entity entity : trackerEntitiesRaw) { - if (entity == null) continue; - - final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity(); - - if (tracker == null) continue; - if (tracker.serverEntity.wantSendDirtyEntityData) { - tracker.serverEntity.wantSendDirtyEntityData = false; - sendDirty.add(tracker.serverEntity); - } - } - if (!sendDirty.isEmpty()) { - level.getServer().execute(new SendChanges(sendDirty.elements(), sendDirty.size())); - } }); } From 29dce8ff3c9f93df274f40869657c5d37da7232c Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Thu, 29 May 2025 17:49:28 +0900 Subject: [PATCH 19/25] fix bee pathfinding --- .../features/0026-Petal-Async-Pathfinding.patch | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0026-Petal-Async-Pathfinding.patch b/leaf-server/minecraft-patches/features/0026-Petal-Async-Pathfinding.patch index f1e7b57a..f8ed6d61 100644 --- a/leaf-server/minecraft-patches/features/0026-Petal-Async-Pathfinding.patch +++ b/leaf-server/minecraft-patches/features/0026-Petal-Async-Pathfinding.patch @@ -19,7 +19,7 @@ This patch was ported downstream from the Petal fork. Makes most pathfinding-related work happen asynchronously diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java -index 14d9dceacc82cc6c085dab8f52e59a318dd8cae5..8b3dfb1385a2252a4aaead5558c0ffbd5c204971 100644 +index b326c87c1c958bb18fc961010768f7d9f0e414cf..73e88f96abee63bd8397575308baac018d12cf26 100644 --- a/net/minecraft/world/entity/Mob.java +++ b/net/minecraft/world/entity/Mob.java @@ -255,6 +255,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab @@ -562,19 +562,18 @@ index 1f96fd5085bacb4c584576c7cb9f51e7898e9b03..8819717c5307a90abc493cf801b4e795 + // Leaf end - Kaiiju - await on async path processing } diff --git a/net/minecraft/world/entity/animal/Bee.java b/net/minecraft/world/entity/animal/Bee.java -index d5727999eb67ff30dbf47865d59452483338e170..6fffa2e98e54ab015762417af8507d11c164e765 100644 +index d5727999eb67ff30dbf47865d59452483338e170..110e49f17eb2c4fdf360263b00f4736de04ffcad 100644 --- a/net/minecraft/world/entity/animal/Bee.java +++ b/net/minecraft/world/entity/animal/Bee.java -@@ -936,7 +936,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { +@@ -936,6 +936,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { } else { Bee.this.pathfindRandomlyTowards(Bee.this.hivePos); } -- } else { -+ } else if (navigation.getPath() != null && navigation.getPath().isProcessed()) { // Kaiiju - petal - check processing ++ //} else if (navigation.getPath() != null && navigation.getPath().isProcessed()) { // Kaiiju - petal - check processing // todo + } else { boolean flag = this.pathfindDirectlyTowards(Bee.this.hivePos); if (!flag) { - this.dropAndBlacklistHive(); -@@ -990,7 +990,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { +@@ -990,7 +991,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { return true; } else { Path path = Bee.this.navigation.getPath(); From 31c42a45b6f86b643d2eae057f98fbc0ca1e17c3 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Thu, 29 May 2025 17:41:47 +0800 Subject: [PATCH 20/25] Use correct way to resolve all world config path Fixes https://github.com/Winds-Studio/Leaf/issues/342 supersedes https://github.com/Winds-Studio/Leaf/pull/344 --- .../src/main/java/org/dreeam/leaf/config/LeafConfig.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java index e40a1c50..ad14e420 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java @@ -227,8 +227,12 @@ public class LeafConfig { extraConfigs.addAll(Arrays.asList(existing.split(","))); } + // Use same way in spark's BukkitServerConfigProvider#getNestedFiles to get all world configs + // It may spam in the spark profiler, but it's ok, since spark uses YamlConfigParser.INSTANCE to + // get configs defined in extra config flag instead of using SplitYamlConfigParser.INSTANCE for (World world : Bukkit.getWorlds()) { - extraConfigs.add(world.getWorldFolder().getName() + "/gale-world.yml"); // Gale world config + Path galeWorldFolder = world.getWorldFolder().toPath().resolve("gale-world.yml"); + extraConfigs.add(galeWorldFolder.toString().replace("\\", "/").replace("./", "")); // Gale world config } return extraConfigs; From 653efdc16599dff5a688d7742db6b53eeddcd302 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Thu, 29 May 2025 19:04:57 +0900 Subject: [PATCH 21/25] fix SlopeDistanceNodeDeque memory leak --- .../features/0139-Use-BFS-on-getSlopeDistance.patch | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0139-Use-BFS-on-getSlopeDistance.patch b/leaf-server/minecraft-patches/features/0139-Use-BFS-on-getSlopeDistance.patch index 33069a99..b36feedf 100644 --- a/leaf-server/minecraft-patches/features/0139-Use-BFS-on-getSlopeDistance.patch +++ b/leaf-server/minecraft-patches/features/0139-Use-BFS-on-getSlopeDistance.patch @@ -24,10 +24,10 @@ index d21ae1a6ce307c186bc7e218b947dd1879d93b00..7f823e7f282e52b7cf918b117a5059ab BlockState blockState = this.getBlockState(pos); FluidState fluidState = blockState.getFluidState(); diff --git a/net/minecraft/world/level/material/FlowingFluid.java b/net/minecraft/world/level/material/FlowingFluid.java -index 4c2c2efd5380ff1fa5ad7553b51babae20f516ae..cf8fd5df3472c3212fd3cf9536761c998aff26d4 100644 +index 4c2c2efd5380ff1fa5ad7553b51babae20f516ae..01ba0756789fcc61905bbaf1be88b526b76f2490 100644 --- a/net/minecraft/world/level/material/FlowingFluid.java +++ b/net/minecraft/world/level/material/FlowingFluid.java -@@ -341,32 +341,117 @@ public abstract class FlowingFluid extends Fluid { +@@ -341,32 +341,123 @@ public abstract class FlowingFluid extends Fluid { protected void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state, BlockPos source) { beforeDestroyingBlock(level, pos, state); } // Paper - Add BlockBreakBlockEvent protected abstract void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state); @@ -38,6 +38,7 @@ index 4c2c2efd5380ff1fa5ad7553b51babae20f516ae..cf8fd5df3472c3212fd3cf9536761c99 + it.unimi.dsi.fastutil.longs.LongSet visited = ((ServerLevel) level).slopeDistanceCacheVisited; + SlopeDistanceNodeDeque queue = ((ServerLevel) level).slopeDistanceCacheQueue; + visited.clear(); ++ queue.clear(); + + for (Direction dir : Direction.Plane.HORIZONTAL) { + if (dir == excludedDirection) continue; @@ -130,6 +131,11 @@ index 4c2c2efd5380ff1fa5ad7553b51babae20f516ae..cf8fd5df3472c3212fd3cf9536761c99 + } + */ + ++ private void clear() { ++ start = 0; ++ end = 0; ++ } ++ + private boolean isEmpty() { + return end == start || (end <= start && length == start - end); + } From a33f10a17afcf68995fc27b46cec0709600bcccb Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Sat, 31 May 2025 17:20:41 +0900 Subject: [PATCH 22/25] fix attribute crash server --- .../features/0085-Multithreaded-Tracker.patch | 245 +++++++++++------- ...ecessary-calculations-if-player-is-n.patch | 6 +- .../0103-Lithium-equipment-tracking.patch | 8 +- .../features/0119-Only-player-pushable.patch | 8 +- ...ptimize-addOrUpdateTransientModifier.patch | 2 +- .../features/0176-Optimize-AttributeMap.patch | 100 ------- ...-Optimize-getScaledTrackingDistance.patch} | 0 ...ptimize-SynchedEntityData-packDirty.patch} | 0 ...patch => 0178-Optimize-isEyeInFluid.patch} | 0 ...tion.patch => 0179-Paw-optimization.patch} | 0 ...patch => 0180-Cache-block-path-type.patch} | 0 ...ch => 0181-optimize-getEntityStatus.patch} | 0 ...on-optimized-PoweredRailBlock-logic.patch} | 0 .../features/0023-Multithreaded-Tracker.patch | 13 + .../util/map/AttributeInstanceArrayMap.java | 60 +++-- .../leaf/util/map/AttributeInstanceSet.java | 113 ++++++++ 16 files changed, 319 insertions(+), 236 deletions(-) delete mode 100644 leaf-server/minecraft-patches/features/0176-Optimize-AttributeMap.patch rename leaf-server/minecraft-patches/features/{0177-Optimize-getScaledTrackingDistance.patch => 0176-Optimize-getScaledTrackingDistance.patch} (100%) rename leaf-server/minecraft-patches/features/{0178-Optimize-SynchedEntityData-packDirty.patch => 0177-Optimize-SynchedEntityData-packDirty.patch} (100%) rename leaf-server/minecraft-patches/features/{0179-Optimize-isEyeInFluid.patch => 0178-Optimize-isEyeInFluid.patch} (100%) rename leaf-server/minecraft-patches/features/{0180-Paw-optimization.patch => 0179-Paw-optimization.patch} (100%) rename leaf-server/minecraft-patches/features/{0181-Cache-block-path-type.patch => 0180-Cache-block-path-type.patch} (100%) rename leaf-server/minecraft-patches/features/{0182-optimize-getEntityStatus.patch => 0181-optimize-getEntityStatus.patch} (100%) rename leaf-server/minecraft-patches/features/{0183-Rail-Optimization-optimized-PoweredRailBlock-logic.patch => 0182-Rail-Optimization-optimized-PoweredRailBlock-logic.patch} (100%) create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceSet.java diff --git a/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch b/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch index b08d943c..f18da995 100644 --- a/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch +++ b/leaf-server/minecraft-patches/features/0085-Multithreaded-Tracker.patch @@ -58,6 +58,18 @@ index dd2509996bfd08e8c3f9f2be042229eac6d7692d..a35e9fae8f8da0c42f0616c4f78dc396 private static final byte CHUNK_TICKET_STAGE_NONE = 0; private static final byte CHUNK_TICKET_STAGE_LOADING = 1; +diff --git a/net/minecraft/network/protocol/game/ClientboundUpdateAttributesPacket.java b/net/minecraft/network/protocol/game/ClientboundUpdateAttributesPacket.java +index 9c0c99b936b4a82ebfe924866e53ec71f7bbe9ad..2ccff968cb2065d34fad4d27573f9e3081edb2f2 100644 +--- a/net/minecraft/network/protocol/game/ClientboundUpdateAttributesPacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundUpdateAttributesPacket.java +@@ -32,6 +32,7 @@ public class ClientboundUpdateAttributesPacket implements Packet updatePacket = savedData.getUpdatePacket(mapId, serverPlayer); -@@ -433,15 +441,30 @@ public class ServerEntity { - } - - if (this.entity instanceof LivingEntity) { -- Set attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync(); -+ // Leaf start - Multithreaded tracker -+ var attributeMap = ((LivingEntity)this.entity).getAttributes(); -+ Set attributesToSync = attributeMap.getAttributesToSync(); - if (!attributesToSync.isEmpty()) { -+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { -+ synchronized (attributeMap) { -+ // CraftBukkit start - Send scaled max health -+ if (this.entity instanceof ServerPlayer serverPlayer) { -+ serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false); -+ } -+ // CraftBukkit end -+ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); -+ -+ } -+ } else { - // CraftBukkit start - Send scaled max health - if (this.entity instanceof ServerPlayer serverPlayer) { - serverPlayer.getBukkitEntity().injectScaledMaxHealth(attributesToSync, false); - } - // CraftBukkit end +@@ -443,7 +451,7 @@ public class ServerEntity { this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); -+ } } -+ // Leaf end - Multithreaded tracker - attributesToSync.clear(); +- attributesToSync.clear(); ++ // attributesToSync.clear(); // Leaf - Multithreaded tracker } + } + diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java index 275b640f4536366152f59acf071dd4eba15696c8..a669a59a42f814480879a52d2da5e04c636720de 100644 --- a/net/minecraft/server/level/ServerLevel.java @@ -418,39 +407,50 @@ index 327b3bc89920c4ab02c1126dc63bca05ce3abefe..1415043bee5fbbfcf9dab9184a9418d5 if (this.player.isRemoved()) { LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java -index d502325d693539842fd6f5485365e0e9b786b7aa..324f8952a921a3897f4ff48145f0f8645c690318 100644 +index d502325d693539842fd6f5485365e0e9b786b7aa..a99527409e9aae6c8b321a5ed100b2645151087e 100644 --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java -@@ -1311,13 +1311,26 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -1311,13 +1311,13 @@ public abstract class LivingEntity extends Entity implements Attackable { } private void refreshDirtyAttributes() { - Set attributesToUpdate = this.getAttributes().getAttributesToUpdate(); + // Leaf start - Multithreaded tracker -+ var attributeMap = this.getAttributes(); -+ Set attributesToUpdate = attributeMap.getAttributesToUpdate(); -+ -+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { -+ synchronized (attributeMap) { -+ for (AttributeInstance attributeInstance : attributesToUpdate) { -+ this.onAttributeUpdated(attributeInstance.getAttribute()); -+ } ++ int[] attributesToUpdate = this.getAttributes().getAttributesToUpdateIds(); -+ attributesToUpdate.clear(); -+ } -+ } else { - for (AttributeInstance attributeInstance : attributesToUpdate) { - this.onAttributeUpdated(attributeInstance.getAttribute()); +- for (AttributeInstance attributeInstance : attributesToUpdate) { +- this.onAttributeUpdated(attributeInstance.getAttribute()); ++ for (int attribute : attributesToUpdate) { ++ this.onAttributeUpdated(net.minecraft.core.registries.BuiltInRegistries.ATTRIBUTE.get(attribute).orElseThrow()); } - - attributesToUpdate.clear(); -+ } +- +- attributesToUpdate.clear(); + // Leaf end - Multithreaded tracker } protected void onAttributeUpdated(Holder attribute) { +diff --git a/net/minecraft/world/entity/ai/attributes/Attribute.java b/net/minecraft/world/entity/ai/attributes/Attribute.java +index f8419dde44ebc7324e783f8bee42132d5ec973c3..406767c60ec1a324faaf5d3658b161647497f99b 100644 +--- a/net/minecraft/world/entity/ai/attributes/Attribute.java ++++ b/net/minecraft/world/entity/ai/attributes/Attribute.java +@@ -16,10 +16,15 @@ public class Attribute { + private boolean syncable; + private final String descriptionId; + private Attribute.Sentiment sentiment = Attribute.Sentiment.POSITIVE; ++ // Leaf start - Optimize AttributeMap ++ public final int uid; ++ private static final java.util.concurrent.atomic.AtomicInteger SIZE = new java.util.concurrent.atomic.AtomicInteger(); ++ // Leaf end - Optimize AttributeMap + + protected Attribute(String descriptionId, double defaultValue) { + this.defaultValue = defaultValue; + this.descriptionId = descriptionId; ++ this.uid = SIZE.getAndAdd(1); // Leaf - Optimize AttributeMap + } + + public double getDefaultValue() { diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -index 8013594bb4844e7a8abf28123958e7f632d39341..93b375f39f10568f6b222607890a9ce67db0e9bb 100644 +index 8013594bb4844e7a8abf28123958e7f632d39341..b502c4a0f3695cc5bee8954f937f64584df1584d 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java @@ -24,8 +24,24 @@ public class AttributeInstance { @@ -495,21 +495,21 @@ index 8013594bb4844e7a8abf28123958e7f632d39341..93b375f39f10568f6b222607890a9ce6 } public Set getModifiers() { -@@ -174,6 +196,13 @@ public class AttributeInstance { - } +@@ -142,8 +164,12 @@ public class AttributeInstance { - public void replaceFrom(AttributeInstance instance) { -+ // Leaf start - Multithreaded tracker -+ synchronized (instance) { -+ this.replaceFrom0(instance); -+ } -+ } -+ public void replaceFrom0(AttributeInstance instance) { -+ // Leaf end - Multithreaded tracker - this.baseValue = instance.baseValue; - this.modifierById.clear(); - this.modifierById.putAll(instance.modifierById); -@@ -195,9 +224,19 @@ public class AttributeInstance { + public double getValue() { + if (this.dirty) { +- this.cachedValue = this.calculateValue(); ++ // Leaf start - Multithreaded tracker ++ double value = this.calculateValue(); ++ this.cachedValue = value; + this.dirty = false; ++ return value; ++ // Leaf end - Multithreaded tracker + } + + return this.cachedValue; +@@ -195,9 +221,19 @@ public class AttributeInstance { if (!this.permanentModifiers.isEmpty()) { ListTag listTag = new ListTag(); @@ -532,65 +532,112 @@ index 8013594bb4844e7a8abf28123958e7f632d39341..93b375f39f10568f6b222607890a9ce6 compoundTag.put("modifiers", listTag); } diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java -index 89f4c5b2d61e27acd48063f9f24ce9ea91898b8b..c0a09b615e9b6c4ec72b8b77a78e7da374d4498b 100644 +index 89f4c5b2d61e27acd48063f9f24ce9ea91898b8b..0bc846721b1af44904a705f5c4aef897a03824e0 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java -@@ -21,8 +21,19 @@ public class AttributeMap { +@@ -20,12 +20,14 @@ import org.slf4j.Logger; + public class AttributeMap { private static final Logger LOGGER = LogUtils.getLogger(); // Gale start - Lithium - replace AI attributes with optimized collections - private final Map, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); +- private final Map, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); - private final Set attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); - private final Set attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); + // Leaf start - Multithreaded tracker -+ private final Set attributesToSync; -+ private final Set attributesToUpdate; -+ { -+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { -+ attributesToSync = it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0), this); -+ attributesToUpdate = it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0), this); -+ } else { -+ attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); -+ attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); -+ } -+ } ++ private final Map, AttributeInstance> attributes = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(); ++ private final org.dreeam.leaf.util.map.AttributeInstanceSet attributesToSync = new org.dreeam.leaf.util.map.AttributeInstanceSet((org.dreeam.leaf.util.map.AttributeInstanceArrayMap) attributes); ++ private final org.dreeam.leaf.util.map.AttributeInstanceSet attributesToUpdate = new org.dreeam.leaf.util.map.AttributeInstanceSet((org.dreeam.leaf.util.map.AttributeInstanceArrayMap) attributes); + // Leaf end - Multithreaded tracker // Gale end - Lithium - replace AI attributes with optimized collections private final AttributeSupplier supplier; - private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations -@@ -60,7 +71,13 @@ public class AttributeMap { +- private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations ++ //private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations // Leaf - Optimize AttributeMap + private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables + + public AttributeMap(AttributeSupplier supplier) { +@@ -36,31 +38,54 @@ public class AttributeMap { + this.entity = entity; + // Purpur end - Ridables + this.supplier = defaultAttributes; +- this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations ++ //this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations // Leaf - Optimize AttributeMap + } + +- private void onAttributeModified(AttributeInstance instance) { ++ // Leaf start - Multithreaded tracker ++ private synchronized void onAttributeModified(AttributeInstance instance) { + this.attributesToUpdate.add(instance); + if (instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))) { // Purpur - Ridables + this.attributesToSync.add(instance); + } + } + +- public Set getAttributesToSync() { +- return this.attributesToSync; ++ private static final AttributeInstance[] EMPTY_ATTRIBUTE_INSTANCE = new AttributeInstance[0]; ++ public synchronized Set getAttributesToSync() { ++ var clone = it.unimi.dsi.fastutil.objects.ReferenceArraySet.ofUnchecked(attributesToSync.toArray(EMPTY_ATTRIBUTE_INSTANCE)); ++ this.attributesToSync.clear(); ++ return clone; + } + +- public Set getAttributesToUpdate() { +- return this.attributesToUpdate; ++ public synchronized Set getAttributesToUpdate() { ++ var clone = it.unimi.dsi.fastutil.objects.ReferenceArraySet.ofUnchecked(attributesToUpdate.toArray(EMPTY_ATTRIBUTE_INSTANCE)); ++ this.attributesToUpdate.clear(); ++ return clone; + } + ++ public synchronized int[] getAttributesToUpdateIds() { ++ int[] clone = attributesToUpdate.inner.toIntArray(); ++ this.attributesToUpdate.clear(); ++ return clone; ++ } ++ // Leaf end - Multithreaded tracker ++ + public Collection getSyncableAttributes() { + return this.attributes.values().stream().filter(instance -> instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))).collect(Collectors.toList()); // Purpur - Ridables + } @Nullable public AttributeInstance getInstance(Holder attribute) { - return this.attributes.computeIfAbsent(attribute, this.createInstance); // Gale - Airplane - reduce entity allocations - cache lambda, as for some reason java allocates it anyways -+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { -+ synchronized (this) { -+ return this.attributes.computeIfAbsent(attribute, holder -> this.supplier.createInstance(this::onAttributeModified, (Holder) holder)); ++ // Leaf start - Multithreaded tracker ++ AttributeInstance v; ++ if ((v = this.attributes.get(attribute)) == null) { ++ AttributeInstance newValue; ++ if ((newValue = this.supplier.createInstance(this::onAttributeModified, attribute)) != null) { ++ attributes.put(attribute, newValue); ++ return newValue; + } -+ } else { -+ return this.attributes.computeIfAbsent(attribute, holder -> this.supplier.createInstance(this::onAttributeModified, (Holder) holder)); + } ++ return v; ++ // Leaf end - Multithreaded tracker } public boolean hasAttribute(Holder attribute) { -@@ -176,8 +193,17 @@ public class AttributeMap { +diff --git a/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java b/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java +index 24710041ccbc70e5506d8d89ae34f0141977f209..05de8a77b389691dd6986f36b4cb8cc0935e21e4 100644 +--- a/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java +@@ -11,7 +11,7 @@ public class AttributeSupplier { + private final Map, AttributeInstance> instances; - // Paper - start - living entity allow attribute registration - public void registerAttribute(Holder attributeBase) { -+ // Leaf start - Multithreaded tracker - AttributeInstance attributeModifiable = new AttributeInstance(attributeBase, AttributeInstance::getAttribute); -- attributes.put(attributeBase, attributeModifiable); -+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { -+ synchronized (this) { -+ attributes.put(attributeBase, attributeModifiable); -+ -+ } -+ } else { -+ attributes.put(attributeBase, attributeModifiable); -+ } -+ // Leaf end - Multithreaded tracker + AttributeSupplier(Map, AttributeInstance> instances) { +- this.instances = instances; ++ this.instances = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(instances); // Leaf - Optimize AttributeMap } - // Paper - end - living entity allow attribute registration + public AttributeInstance getAttributeInstance(Holder attribute) { +@@ -41,7 +41,7 @@ public class AttributeSupplier { + } + + @Nullable +- public AttributeInstance createInstance(Consumer onDirty, Holder attribute) { ++ public AttributeInstance createInstance(Consumer onDirty, Holder attribute) { // Leaf - Multithreaded tracker + AttributeInstance attributeInstance = this.instances.get(attribute); + if (attributeInstance == null) { + return null; diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java index c96f458994818392857642282ec3d492124885da..d345afd14ef6fe2f0a584df5dfa080fd7ab3f47e 100644 --- a/net/minecraft/world/entity/item/PrimedTnt.java diff --git a/leaf-server/minecraft-patches/features/0097-Lithium-Skip-unnecessary-calculations-if-player-is-n.patch b/leaf-server/minecraft-patches/features/0097-Lithium-Skip-unnecessary-calculations-if-player-is-n.patch index 23754ae5..855e6d09 100644 --- a/leaf-server/minecraft-patches/features/0097-Lithium-Skip-unnecessary-calculations-if-player-is-n.patch +++ b/leaf-server/minecraft-patches/features/0097-Lithium-Skip-unnecessary-calculations-if-player-is-n.patch @@ -12,10 +12,10 @@ As part of: Lithium (https://github.com/CaffeineMC/lithium-fabric) Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java -index 324f8952a921a3897f4ff48145f0f8645c690318..3e3fdcff3725841c8b04047a91733c8b98187e0b 100644 +index 0ac8b7bd9d899daf61aeb58f80bdcebe87974d51..eb79e6984810410c646d7b1910694d7df75dccba 100644 --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java -@@ -2752,6 +2752,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -2739,6 +2739,7 @@ public abstract class LivingEntity extends Entity implements Attackable { } protected void updateSwingTime() { @@ -23,7 +23,7 @@ index 324f8952a921a3897f4ff48145f0f8645c690318..3e3fdcff3725841c8b04047a91733c8b int currentSwingDuration = this.getCurrentSwingDuration(); if (this.swinging) { this.swingTime++; -@@ -3703,6 +3704,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -3690,6 +3691,7 @@ public abstract class LivingEntity extends Entity implements Attackable { protected void updateFallFlying() { this.checkSlowFallDistance(); if (!this.level().isClientSide) { diff --git a/leaf-server/minecraft-patches/features/0103-Lithium-equipment-tracking.patch b/leaf-server/minecraft-patches/features/0103-Lithium-equipment-tracking.patch index ba404481..25b5eb45 100644 --- a/leaf-server/minecraft-patches/features/0103-Lithium-equipment-tracking.patch +++ b/leaf-server/minecraft-patches/features/0103-Lithium-equipment-tracking.patch @@ -76,7 +76,7 @@ index a8c6549f772208cd543607224fef2c2389b14f24..709631db548a16a969a373e26ebbcd69 public boolean equals(Object other) { return this == other diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java -index 3e3fdcff3725841c8b04047a91733c8b98187e0b..ffae6778b471628639c41b1d5f3af08b6b72a86c 100644 +index eb79e6984810410c646d7b1910694d7df75dccba..69e91a732a497b2ffa906088a636947ce9a9ec36 100644 --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java @@ -159,7 +159,7 @@ import org.bukkit.event.entity.EntityTeleportEvent; @@ -116,7 +116,7 @@ index 3e3fdcff3725841c8b04047a91733c8b98187e0b..ffae6778b471628639c41b1d5f3af08b Equippable equippable = newItem.get(DataComponents.EQUIPPABLE); if (!this.isSilent() && equippable != null && slot == equippable.slot() && !silent) { // CraftBukkit this.level() -@@ -3368,6 +3373,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -3355,6 +3360,7 @@ public abstract class LivingEntity extends Entity implements Attackable { public void detectEquipmentUpdatesPublic() { // CraftBukkit Map map = this.collectEquipmentChanges(); if (map != null) { @@ -124,7 +124,7 @@ index 3e3fdcff3725841c8b04047a91733c8b98187e0b..ffae6778b471628639c41b1d5f3af08b this.handleHandSwap(map); if (!map.isEmpty()) { this.handleEquipmentChanges(map); -@@ -3377,6 +3383,10 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -3364,6 +3370,10 @@ public abstract class LivingEntity extends Entity implements Attackable { @Nullable private Map collectEquipmentChanges() { @@ -135,7 +135,7 @@ index 3e3fdcff3725841c8b04047a91733c8b98187e0b..ffae6778b471628639c41b1d5f3af08b Map map = null; // Paper start - EntityEquipmentChangedEvent record EquipmentChangeImpl(org.bukkit.inventory.ItemStack oldItem, org.bukkit.inventory.ItemStack newItem) implements io.papermc.paper.event.entity.EntityEquipmentChangedEvent.EquipmentChange { -@@ -4736,6 +4746,81 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -4723,6 +4733,81 @@ public abstract class LivingEntity extends Entity implements Attackable { return this.lastHurtByPlayerTime; } diff --git a/leaf-server/minecraft-patches/features/0119-Only-player-pushable.patch b/leaf-server/minecraft-patches/features/0119-Only-player-pushable.patch index df97992b..1f472084 100644 --- a/leaf-server/minecraft-patches/features/0119-Only-player-pushable.patch +++ b/leaf-server/minecraft-patches/features/0119-Only-player-pushable.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Only player pushable Useful for extreme cases like massive entities collide together in a small area diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java -index ffae6778b471628639c41b1d5f3af08b6b72a86c..32039b50946b23e4f55498aa09ea33cfa77b517c 100644 +index 69e91a732a497b2ffa906088a636947ce9a9ec36..d455406df1f3a4c3d1bd016d50d1d7025366ae80 100644 --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java -@@ -3644,7 +3644,7 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf +@@ -3631,7 +3631,7 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf this.checkAutoSpinAttack(boundingBox, this.getBoundingBox()); } @@ -18,7 +18,7 @@ index ffae6778b471628639c41b1d5f3af08b6b72a86c..32039b50946b23e4f55498aa09ea33cf // Paper start - Add EntityMoveEvent // Purpur start - Ridables if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { -@@ -3782,7 +3782,14 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf +@@ -3769,7 +3769,14 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf return; } // Paper end - don't run getEntities if we're not going to use its result @@ -34,7 +34,7 @@ index ffae6778b471628639c41b1d5f3af08b6b72a86c..32039b50946b23e4f55498aa09ea33cf if (!entities.isEmpty()) { // Paper - don't run getEntities if we're not going to use its result; moved up if (_int > 0 && entities.size() > _int - 1 && this.random.nextInt(4) == 0) { -@@ -3815,6 +3822,44 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf +@@ -3802,6 +3809,44 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf } } diff --git a/leaf-server/minecraft-patches/features/0143-Optimize-addOrUpdateTransientModifier.patch b/leaf-server/minecraft-patches/features/0143-Optimize-addOrUpdateTransientModifier.patch index 7bee8026..7b58130e 100644 --- a/leaf-server/minecraft-patches/features/0143-Optimize-addOrUpdateTransientModifier.patch +++ b/leaf-server/minecraft-patches/features/0143-Optimize-addOrUpdateTransientModifier.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Optimize addOrUpdateTransientModifier diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -index 93b375f39f10568f6b222607890a9ce67db0e9bb..69ba880e17e5cff3b2260f539683ca565f34e8dd 100644 +index ed5077708415a74da171b88fa1fb8b736446666b..62cadad97109247e65a550acc5955424b1f6fc5e 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java @@ -107,8 +107,13 @@ public class AttributeInstance { diff --git a/leaf-server/minecraft-patches/features/0176-Optimize-AttributeMap.patch b/leaf-server/minecraft-patches/features/0176-Optimize-AttributeMap.patch deleted file mode 100644 index b22d676f..00000000 --- a/leaf-server/minecraft-patches/features/0176-Optimize-AttributeMap.patch +++ /dev/null @@ -1,100 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: hayanesuru -Date: Thu, 15 May 2025 21:11:18 +0900 -Subject: [PATCH] Optimize AttributeMap - - -diff --git a/net/minecraft/world/entity/ai/attributes/Attribute.java b/net/minecraft/world/entity/ai/attributes/Attribute.java -index f8419dde44ebc7324e783f8bee42132d5ec973c3..406767c60ec1a324faaf5d3658b161647497f99b 100644 ---- a/net/minecraft/world/entity/ai/attributes/Attribute.java -+++ b/net/minecraft/world/entity/ai/attributes/Attribute.java -@@ -16,10 +16,15 @@ public class Attribute { - private boolean syncable; - private final String descriptionId; - private Attribute.Sentiment sentiment = Attribute.Sentiment.POSITIVE; -+ // Leaf start - Optimize AttributeMap -+ public final int uid; -+ private static final java.util.concurrent.atomic.AtomicInteger SIZE = new java.util.concurrent.atomic.AtomicInteger(); -+ // Leaf end - Optimize AttributeMap - - protected Attribute(String descriptionId, double defaultValue) { - this.defaultValue = defaultValue; - this.descriptionId = descriptionId; -+ this.uid = SIZE.getAndAdd(1); // Leaf - Optimize AttributeMap - } - - public double getDefaultValue() { -diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java -index c0a09b615e9b6c4ec72b8b77a78e7da374d4498b..a550e6bd9dffa9a46f0d7967c9d73ff5cbfbaa0a 100644 ---- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java -+++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java -@@ -20,7 +20,7 @@ import org.slf4j.Logger; - public class AttributeMap { - private static final Logger LOGGER = LogUtils.getLogger(); - // Gale start - Lithium - replace AI attributes with optimized collections -- private final Map, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); -+ private final Map, AttributeInstance> attributes = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(); // Leaf - Optimize AttributeMap - // Leaf start - Multithreaded tracker - private final Set attributesToSync; - private final Set attributesToUpdate; -@@ -36,7 +36,7 @@ public class AttributeMap { - // Leaf end - Multithreaded tracker - // Gale end - Lithium - replace AI attributes with optimized collections - private final AttributeSupplier supplier; -- private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations -+ //private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations // Leaf - Optimize AttributeMap - private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables - - public AttributeMap(AttributeSupplier supplier) { -@@ -47,7 +47,7 @@ public class AttributeMap { - this.entity = entity; - // Purpur end - Ridables - this.supplier = defaultAttributes; -- this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations -+ //this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations // Leaf - Optimize AttributeMap - } - - private void onAttributeModified(AttributeInstance instance) { -@@ -71,13 +71,24 @@ public class AttributeMap { - - @Nullable - public AttributeInstance getInstance(Holder attribute) { -- if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { -- synchronized (this) { -- return this.attributes.computeIfAbsent(attribute, holder -> this.supplier.createInstance(this::onAttributeModified, (Holder) holder)); -+ // Leaf start - Optimize AttributeMap -+ AttributeInstance v; -+ if ((v = this.attributes.get(attribute)) == null) { -+ AttributeInstance newValue; -+ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { -+ synchronized (this) { -+ if ((newValue = this.supplier.createInstance(this::onAttributeModified, attribute)) != null) { -+ this.attributes.put(attribute, newValue); -+ return newValue; -+ } -+ } -+ } else if ((newValue = this.supplier.createInstance(this::onAttributeModified, attribute)) != null) { -+ this.attributes.put(attribute, newValue); -+ return newValue; - } -- } else { -- return this.attributes.computeIfAbsent(attribute, holder -> this.supplier.createInstance(this::onAttributeModified, (Holder) holder)); - } -+ return v; -+ // Leaf end - Optimize AttributeMap - } - - public boolean hasAttribute(Holder attribute) { -diff --git a/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java b/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java -index 24710041ccbc70e5506d8d89ae34f0141977f209..09341ef6c651150aba223689badbead490162b2b 100644 ---- a/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java -+++ b/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java -@@ -11,7 +11,7 @@ public class AttributeSupplier { - private final Map, AttributeInstance> instances; - - AttributeSupplier(Map, AttributeInstance> instances) { -- this.instances = instances; -+ this.instances = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(instances); // Leaf - Optimize AttributeMap - } - - public AttributeInstance getAttributeInstance(Holder attribute) { diff --git a/leaf-server/minecraft-patches/features/0177-Optimize-getScaledTrackingDistance.patch b/leaf-server/minecraft-patches/features/0176-Optimize-getScaledTrackingDistance.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0177-Optimize-getScaledTrackingDistance.patch rename to leaf-server/minecraft-patches/features/0176-Optimize-getScaledTrackingDistance.patch diff --git a/leaf-server/minecraft-patches/features/0178-Optimize-SynchedEntityData-packDirty.patch b/leaf-server/minecraft-patches/features/0177-Optimize-SynchedEntityData-packDirty.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0178-Optimize-SynchedEntityData-packDirty.patch rename to leaf-server/minecraft-patches/features/0177-Optimize-SynchedEntityData-packDirty.patch diff --git a/leaf-server/minecraft-patches/features/0179-Optimize-isEyeInFluid.patch b/leaf-server/minecraft-patches/features/0178-Optimize-isEyeInFluid.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0179-Optimize-isEyeInFluid.patch rename to leaf-server/minecraft-patches/features/0178-Optimize-isEyeInFluid.patch diff --git a/leaf-server/minecraft-patches/features/0180-Paw-optimization.patch b/leaf-server/minecraft-patches/features/0179-Paw-optimization.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0180-Paw-optimization.patch rename to leaf-server/minecraft-patches/features/0179-Paw-optimization.patch diff --git a/leaf-server/minecraft-patches/features/0181-Cache-block-path-type.patch b/leaf-server/minecraft-patches/features/0180-Cache-block-path-type.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0181-Cache-block-path-type.patch rename to leaf-server/minecraft-patches/features/0180-Cache-block-path-type.patch diff --git a/leaf-server/minecraft-patches/features/0182-optimize-getEntityStatus.patch b/leaf-server/minecraft-patches/features/0181-optimize-getEntityStatus.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0182-optimize-getEntityStatus.patch rename to leaf-server/minecraft-patches/features/0181-optimize-getEntityStatus.patch diff --git a/leaf-server/minecraft-patches/features/0183-Rail-Optimization-optimized-PoweredRailBlock-logic.patch b/leaf-server/minecraft-patches/features/0182-Rail-Optimization-optimized-PoweredRailBlock-logic.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0183-Rail-Optimization-optimized-PoweredRailBlock-logic.patch rename to leaf-server/minecraft-patches/features/0182-Rail-Optimization-optimized-PoweredRailBlock-logic.patch diff --git a/leaf-server/paper-patches/features/0023-Multithreaded-Tracker.patch b/leaf-server/paper-patches/features/0023-Multithreaded-Tracker.patch index 39c5b084..f70a9251 100644 --- a/leaf-server/paper-patches/features/0023-Multithreaded-Tracker.patch +++ b/leaf-server/paper-patches/features/0023-Multithreaded-Tracker.patch @@ -71,6 +71,19 @@ index edcd209798740f31cb302f36d7864a0d8ea1d561..e8b80ac3d2a54b9e855cad80c8a78200 set.add(connection.getPlayer().getBukkitEntity().getPlayer()); } return set; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 8e8630db9f74e5952142dba14ec58917c5745287..f89f6cea94486842a5e9015200a0d648225c8615 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2915,7 +2915,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + Iterator iterator = collection.iterator(); + while (iterator.hasNext()) { + AttributeInstance genericInstance = iterator.next(); +- if (genericInstance.getAttribute() == Attributes.MAX_HEALTH) { ++ if (genericInstance != null && genericInstance.getAttribute() == Attributes.MAX_HEALTH) { // Leaf - Multithreaded tracker + iterator.remove(); + break; + } diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index e52479f3c888268fd1febeb78e9965af834a8ae9..c2552c3706831f7012b5b449fa43c7d5990056a4 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceArrayMap.java b/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceArrayMap.java index 9d4619c2..3464700c 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceArrayMap.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceArrayMap.java @@ -11,7 +11,7 @@ import java.util.*; import java.util.AbstractMap.SimpleEntry; // fast array backend map with O(1) get & put & remove -public class AttributeInstanceArrayMap implements Map, AttributeInstance>, Cloneable { +public final class AttributeInstanceArrayMap implements Map, AttributeInstance>, Cloneable { private int size = 0; private transient AttributeInstance[] a = new AttributeInstance[32]; @@ -46,17 +46,17 @@ public class AttributeInstanceArrayMap implements Map, Attribu } @Override - public final int size() { + public int size() { return size; } @Override - public final boolean isEmpty() { + public boolean isEmpty() { return size == 0; } @Override - public final boolean containsKey(Object key) { + public boolean containsKey(Object key) { if (key instanceof Holder holder && holder.value() instanceof Attribute attribute) { int uid = attribute.uid; return uid >= 0 && uid < a.length && a[uid] != null; @@ -65,22 +65,22 @@ public class AttributeInstanceArrayMap implements Map, Attribu } @Override - public final boolean containsValue(Object value) { - for (final AttributeInstance instance : a) { - if (Objects.equals(value, instance)) { - return true; - } - } - return false; + public boolean containsValue(Object value) { + return value instanceof AttributeInstance val && Objects.equals(getInstance(val.getAttribute().value().uid), val); } @Override - public final AttributeInstance get(Object key) { + public AttributeInstance get(Object key) { return key instanceof Holder holder && holder.value() instanceof Attribute attribute ? a[attribute.uid] : null; } + @Nullable + public AttributeInstance getInstance(int key) { + return a[key]; + } + @Override - public final AttributeInstance put(@NotNull Holder key, AttributeInstance value) { + public AttributeInstance put(@NotNull Holder key, AttributeInstance value) { int uid = key.value().uid; AttributeInstance prev = a[uid]; setByIndex(uid, value); @@ -88,7 +88,7 @@ public class AttributeInstanceArrayMap implements Map, Attribu } @Override - public final AttributeInstance remove(Object key) { + public AttributeInstance remove(Object key) { if (!(key instanceof Holder holder) || !(holder.value() instanceof Attribute attribute)) return null; int uid = attribute.uid; AttributeInstance prev = a[uid]; @@ -97,7 +97,7 @@ public class AttributeInstanceArrayMap implements Map, Attribu } @Override - public final void putAll(@NotNull Map, ? extends AttributeInstance> m) { + public void putAll(@NotNull Map, ? extends AttributeInstance> m) { for (AttributeInstance e : m.values()) { if (e != null) { setByIndex(e.getAttribute().value().uid, e); @@ -106,13 +106,13 @@ public class AttributeInstanceArrayMap implements Map, Attribu } @Override - public final void clear() { + public void clear() { Arrays.fill(a, null); size = 0; } @Override - public final @NotNull Set> keySet() { + public @NotNull Set> keySet() { if (keys == null) { keys = new KeySet(); } @@ -120,7 +120,7 @@ public class AttributeInstanceArrayMap implements Map, Attribu } @Override - public final @NotNull Collection values() { + public @NotNull Collection values() { if (values == null) { values = new Values(); } @@ -128,7 +128,7 @@ public class AttributeInstanceArrayMap implements Map, Attribu } @Override - public final @NotNull Set, AttributeInstance>> entrySet() { + public @NotNull Set, AttributeInstance>> entrySet() { if (entries == null) { entries = new EntrySet(); } @@ -136,13 +136,23 @@ public class AttributeInstanceArrayMap implements Map, Attribu } @Override - public final boolean equals(Object o) { - if (!(o instanceof AttributeInstanceArrayMap that)) return false; - return size == that.size && Arrays.equals(a, that.a); + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof Map s)) return false; + if (s.size() != size()) return false; + if (o instanceof AttributeInstanceArrayMap that) { + return Arrays.equals(a, that.a); + } + for (Entry e : s.entrySet()) { + if (!Objects.equals(get(e.getKey()), e.getValue())) { + return false; + } + } + return true; } @Override - public final int hashCode() { + public int hashCode() { return Arrays.hashCode(a); } @@ -192,7 +202,7 @@ public class AttributeInstanceArrayMap implements Map, Attribu if (!hasNext()) throw new NoSuchElementException(); currentIndex = nextIndex; nextIndex = findNextOccupied(nextIndex + 1); - return BuiltInRegistries.ATTRIBUTE.asHolderIdMap().byIdOrThrow(currentIndex); + return BuiltInRegistries.ATTRIBUTE.get(currentIndex).orElseThrow(); } @Override @@ -279,7 +289,7 @@ public class AttributeInstanceArrayMap implements Map, Attribu public Entry, AttributeInstance> next() { if (!hasNext()) throw new NoSuchElementException(); currentIndex = nextIndex; - Holder key = BuiltInRegistries.ATTRIBUTE.asHolderIdMap().byIdOrThrow(nextIndex); + Holder key = BuiltInRegistries.ATTRIBUTE.get(nextIndex).orElseThrow(); AttributeInstance value = a[nextIndex]; nextIndex = findNextOccupied(nextIndex + 1); return new SimpleEntry<>(key, value) { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceSet.java b/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceSet.java new file mode 100644 index 00000000..5b1f32da --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceSet.java @@ -0,0 +1,113 @@ +package org.dreeam.leaf.util.map; + +import it.unimi.dsi.fastutil.ints.IntArraySet; +import it.unimi.dsi.fastutil.ints.IntSet; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Array; +import java.util.*; + +public final class AttributeInstanceSet extends AbstractCollection implements Set { + public final IntSet inner; + public final AttributeInstanceArrayMap map; + + public AttributeInstanceSet(AttributeInstanceArrayMap map) { + this.map = map; + inner = new IntArraySet(); + } + + @Override + public boolean add(AttributeInstance instance) { + return inner.add(instance.getAttribute().value().uid); + } + + @Override + public boolean remove(Object o) { + return o instanceof AttributeInstance instance && inner.remove(instance.getAttribute().value().uid); + } + + @Override + public @NotNull Iterator iterator() { + return new CloneIterator(inner.toIntArray(), map); + } + + @Override + public int size() { + return inner.size(); + } + + @Override + public boolean isEmpty() { + return inner.isEmpty(); + } + + @Override + public void clear() { + inner.clear(); + } + + @Override + public boolean contains(Object o) { + if (o instanceof AttributeInstance instance) { + return inner.contains(instance.getAttribute().value().uid); + } + return false; + } + + @Override + public AttributeInstance @NotNull [] toArray() { + int[] innerClone = inner.toIntArray(); + AttributeInstance[] arr = new AttributeInstance[innerClone.length]; + for (int i = 0; i < arr.length; i++) { + arr[i] = map.getInstance(innerClone[i]); + } + return arr; + } + + @SuppressWarnings({"unchecked"}) + @Override + public T @NotNull [] toArray(T[] a) { + if (a == null || (a.getClass() == AttributeInstance[].class && a.length == 0)) { + return (T[]) toArray(); + } + if (a.length < size()) { + a = (T[]) Array.newInstance(a.getClass().getComponentType(), size()); + } + System.arraycopy((T[]) toArray(), 0, a, 0, size()); + if (a.length > size()) { + a[size()] = null; + } + return a; + } + + static class CloneIterator implements Iterator { + private final int[] array; + private int index = 0; + private final AttributeInstanceArrayMap map; + + CloneIterator(int[] array, AttributeInstanceArrayMap map) { + this.array = array; + this.map = map; + } + + @Override + public boolean hasNext() { + return index < array.length; + } + + @Override + public AttributeInstance next() { + if (!hasNext()) throw new NoSuchElementException(); + return map.getInstance(array[index++]); + } + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof Set s)) return false; + if (s.size() != size()) return false; + return containsAll(s); + } +} From 2fd5e7bc12a2ca93949a3c00ea5bce1fb23d4ac4 Mon Sep 17 00:00:00 2001 From: Taiyou06 Date: Sun, 1 Jun 2025 21:21:21 +0200 Subject: [PATCH 23/25] optimise player movement further --- .../0038-optimise-ReferenceList.patch | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 leaf-server/paper-patches/features/0038-optimise-ReferenceList.patch diff --git a/leaf-server/paper-patches/features/0038-optimise-ReferenceList.patch b/leaf-server/paper-patches/features/0038-optimise-ReferenceList.patch new file mode 100644 index 00000000..66fcb91c --- /dev/null +++ b/leaf-server/paper-patches/features/0038-optimise-ReferenceList.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sun, 1 Jun 2025 18:13:24 +0200 +Subject: [PATCH] optimise ReferenceList + + +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java +index 8df9406b77eb3c225ebf88bf76a7adb666452f3b..14ac2a533e0b882f26ee4a11f8d6bccfe752c750 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java +@@ -47,17 +47,21 @@ public final class ReferenceList implements Iterable { + + // move the object at the end to this index + final int endIndex = --this.count; +- final E end = (E)this.references[endIndex]; + if (index != endIndex) { ++ // The removed element was not the last one. ++ // Move the element that was at 'endIndex' (the old tail) to 'index'. ++ final E end = (E)this.references[endIndex]; + // not empty after this call + this.referenceToIndex.put(end, index); // update index ++ this.references[index] = end; + } +- this.references[index] = end; ++ // Null out the slot at 'endIndex'. ++ // If 'index == endIndex', this was the slot of the removed element. ++ // If 'index != endIndex', this was the original slot of the moved element 'end'. + this.references[endIndex] = null; + + return true; + } +- + public boolean add(final E obj) { + final int count = this.count; + final int currIndex = this.referenceToIndex.putIfAbsent(obj, count); From bec3d4c63b473a2ae99237fe08f2028daad54724 Mon Sep 17 00:00:00 2001 From: Taiyou <77050201+Taiyou06@users.noreply.github.com> Date: Mon, 2 Jun 2025 19:18:29 +0200 Subject: [PATCH 24/25] Async Data Read for Parallel World Ticking (#333) * initial * improve compat further * cleanup and shit * more cleanup * rebase :3 * increase task queue size * [ci skip] rebuild patches * Optimise BlockEntity tickersInLevel * rebase * [ci skip] cleanup * cleanup * cleanup * clear the buffer at shutdown --------- Co-authored-by: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Co-authored-by: hayanesuru --- ...-SparklyPaper-Parallel-world-ticking.patch | 187 ++- ...5-SparklyPaper-Track-each-world-MSPT.patch | 8 +- .../0139-Use-BFS-on-getSlopeDistance.patch | 4 +- ...-Micro-optimizations-for-random-tick.patch | 6 +- .../features/0153-Async-target-finding.patch | 214 ++-- .../features/0163-Protocol-Core.patch | 6 +- .../features/0179-Paw-optimization.patch | 58 +- ...-SparklyPaper-Parallel-world-ticking.patch | 1118 +++++++++++++++-- .../leaf/async/world/ReadOperationType.java | 11 + .../leaf/async/world/WorldReadRequest.java | 10 + .../SparklyPaperParallelWorldTicking.java | 27 +- 11 files changed, 1356 insertions(+), 293 deletions(-) create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/async/world/ReadOperationType.java create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/async/world/WorldReadRequest.java diff --git a/leaf-server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch b/leaf-server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch index ff920aca..d9544927 100644 --- a/leaf-server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch +++ b/leaf-server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch @@ -265,7 +265,7 @@ index 5ab2c8333178335515e619b87ae420f948c83bd1..be3b2f023897a8823560ee059cb16ec9 } diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index c50a301a0c2365c2052aefc6a23fcf6fa82e1b9d..ac751d460ae0c8dbb858c4047c459a11b57ae175 100644 +index c50a301a0c2365c2052aefc6a23fcf6fa82e1b9d..7f791ff5bdf6c29c78f863d21af16270a74d8e7e 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -291,6 +291,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop serverPlayer1.connection.suspendFlushing()); this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit -@@ -1743,28 +1768,50 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop, ServerLevel> oldLevels = this.levels; Map, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels); newLevels.remove(level.dimension()); @@ -568,7 +581,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 a66e5f6652d9633c856490de36d8d8fdf8a5298a..60e0296312030d25f917c568c17ce86d08e18122 100644 +index a66e5f6652d9633c856490de36d8d8fdf8a5298a..d6524d5c442555eaeb4d90f6a101262ee669f0d9 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 @@ -580,23 +593,173 @@ index a66e5f6652d9633c856490de36d8d8fdf8a5298a..60e0296312030d25f917c568c17ce86d // Paper - rewrite chunk system private final GameEventDispatcher gameEventDispatcher; public boolean noSave; -@@ -208,6 +208,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -208,7 +208,9 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe private double preciseTime; // Purpur - Configurable daylight cycle private boolean forceTime; // Purpur - Configurable daylight cycle private final RandomSequences randomSequences; +- + public java.util.concurrent.ExecutorService tickExecutor; // SparklyPaper - parallel world ticking - ++ public final java.util.concurrent.ConcurrentLinkedQueue asyncReadRequestQueue = new java.util.concurrent.ConcurrentLinkedQueue<>(); // Leaf - SparklyPaper - parallel world ticking ++ private volatile boolean isShuttingDown = false; // Leaf - SparklyPaper - parallel world ticking - Shutdown handling for async reads // CraftBukkit start public final LevelStorageSource.LevelStorageAccess levelStorageAccess; -@@ -703,6 +704,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + public final UUID uuid; +@@ -703,8 +705,26 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle this.realPlayers = Lists.newArrayList(); // Leaves - skip + this.tickExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new org.dreeam.leaf.async.world.SparklyPaperServerLevelTickExecutorThreadFactory(getWorld().getName())); // SparklyPaper - parallel world ticking ++ } ++ ++ // Leaf start - SparklyPaper - parallel world ticking - Shutdown handling for async reads ++ public boolean isShuttingDown() { ++ return this.isShuttingDown; } ++ public void prepareShutdown() { ++ this.isShuttingDown = true; ++ org.dreeam.leaf.async.world.WorldReadRequest req; ++ int clearedRequests = 0; ++ while ((req = this.asyncReadRequestQueue.poll()) != null) { ++ req.future().completeExceptionally(new IllegalStateException("World " + this.getWorld().getName() + " is shutting down. Cannot process buffered read: " + req.type())); ++ clearedRequests++; ++ } ++ if (clearedRequests > 0) MinecraftServer.LOGGER.info("PWT: Cleared " + clearedRequests + " pending async read requests for world " + this.getWorld().getName() + " during shutdown."); ++ } ++ // Leaf end - SparklyPaper - parallel world ticking - Shutdown handling for async reads ++ // Paper start -@@ -1313,9 +1315,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + @Override + public boolean hasChunk(int chunkX, int chunkZ) { +@@ -737,8 +757,112 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + public Player[] eligibleDespawnCheckingPlayerCache = new Player[0]; // Leaf - Cache eligible players for despawn checks + ++ // Leaf start - SparklyPaper - parallel world ticking ++ private void processAsyncReadRequests() { ++ // Only process if parallel ticking is enabled and buffering is active ++ if (!org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled || ++ !org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling.equals("BUFFERED")) { ++ // Clear queue if buffering gets disabled to prevent memory leaks ++ if (!this.asyncReadRequestQueue.isEmpty()) { ++ org.dreeam.leaf.async.world.WorldReadRequest req; ++ while ((req = this.asyncReadRequestQueue.poll()) != null) { ++ req.future().completeExceptionally(new IllegalStateException("Async read buffering disabled while request was pending.")); ++ } ++ } ++ return; ++ } ++ ++ org.dreeam.leaf.async.world.WorldReadRequest request; ++ int processed = 0; ++ // Limit processing per tick to avoid stalling the tick loop ++ int maxToProcess = 16384; // Consider making this configurable ++ ++ while (processed < maxToProcess && (request = this.asyncReadRequestQueue.poll()) != null) { ++ processed++; ++ // Ensure we are on the correct thread before executing ++ // This check might be redundant if called correctly from tick(), but good for safety ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Processing async read request off-thread"); ++ ++ try { ++ Object result = executeReadRequest(request); ++ request.future().complete(result); ++ } catch (Throwable t) { ++ // Log the error from the tick thread side ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Exception processing buffered async world read for type " + request.type(), t); ++ request.future().completeExceptionally(t); ++ } ++ } ++ } ++ ++ // Executes the actual read operation based on the request type ++ private Object executeReadRequest(org.dreeam.leaf.async.world.WorldReadRequest request) { ++ Object[] params = request.params(); ++ BlockPos pos; // Declare pos outside the switch ++ ++ switch (request.type()) { ++ case BLOCK_GET_NMS_STATE: { // ++ pos = (BlockPos) params[0]; ++ return this.getBlockState(pos); ++ } ++ case BLOCK_GET_BIOME: { ++ pos = (BlockPos) params[0]; ++ return this.getNoiseBiome(pos.getX() >> 2, pos.getY() >> 2, pos.getZ() >> 2); ++ } ++ case BLOCK_GET_COMPUTED_BIOME: { ++ pos = (BlockPos) params[0]; ++ return this.getBiome(pos); ++ } ++ case BLOCK_IS_INDIRECTLY_POWERED: { ++ pos = (BlockPos) params[0]; ++ return this.hasNeighborSignal(pos); ++ } ++ case BLOCK_GET_BLOCK_POWER: { ++ pos = (BlockPos) params[0]; ++ org.bukkit.block.BlockFace face = (org.bukkit.block.BlockFace) params[1]; ++ int power = 0; ++ Direction notchDir = org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(face); ++ ++ if ((face == org.bukkit.block.BlockFace.DOWN || face == org.bukkit.block.BlockFace.SELF) && this.hasSignal(pos.below(), Direction.DOWN)) power = org.bukkit.craftbukkit.block.CraftBlock.getPower(power, this.getBlockState(pos.below())); ++ if ((face == org.bukkit.block.BlockFace.UP || face == org.bukkit.block.BlockFace.SELF) && this.hasSignal(pos.above(), Direction.UP)) power = org.bukkit.craftbukkit.block.CraftBlock.getPower(power, this.getBlockState(pos.above())); ++ if ((face == org.bukkit.block.BlockFace.EAST || face == org.bukkit.block.BlockFace.SELF) && this.hasSignal(pos.east(), Direction.EAST)) power = org.bukkit.craftbukkit.block.CraftBlock.getPower(power, this.getBlockState(pos.east())); ++ if ((face == org.bukkit.block.BlockFace.WEST || face == org.bukkit.block.BlockFace.SELF) && this.hasSignal(pos.west(), Direction.WEST)) power = org.bukkit.craftbukkit.block.CraftBlock.getPower(power, this.getBlockState(pos.west())); ++ if ((face == org.bukkit.block.BlockFace.NORTH || face == org.bukkit.block.BlockFace.SELF) && this.hasSignal(pos.north(), Direction.NORTH)) power = org.bukkit.craftbukkit.block.CraftBlock.getPower(power, this.getBlockState(pos.north())); ++ if ((face == org.bukkit.block.BlockFace.SOUTH || face == org.bukkit.block.BlockFace.SELF) && this.hasSignal(pos.south(), Direction.SOUTH)) power = org.bukkit.craftbukkit.block.CraftBlock.getPower(power, this.getBlockState(pos.south())); ++ ++ boolean indirectlyPowered = (face == org.bukkit.block.BlockFace.SELF) ? this.hasNeighborSignal(pos) : (this.getSignal(pos, notchDir) > 0); // Simplified indirect check for faces ++ return power > 0 ? power : (indirectlyPowered ? 15 : 0); ++ } ++ case BLOCK_RAY_TRACE: { ++ pos = (BlockPos) params[0]; ++ org.bukkit.Location start = (org.bukkit.Location) params[1]; ++ org.bukkit.util.Vector direction = (org.bukkit.util.Vector) params[2]; ++ double maxDistance = (double) params[3]; ++ org.bukkit.FluidCollisionMode fluidCollisionMode = (org.bukkit.FluidCollisionMode) params[4]; ++ ++ org.bukkit.util.Vector dir = direction.clone().normalize().multiply(maxDistance); ++ Vec3 startPos = org.bukkit.craftbukkit.util.CraftLocation.toVec3D(start); ++ Vec3 endPos = startPos.add(dir.getX(), dir.getY(), dir.getZ()); ++ ++ return this.clip(new net.minecraft.world.level.ClipContext(startPos, endPos, net.minecraft.world.level.ClipContext.Block.OUTLINE, org.bukkit.craftbukkit.CraftFluidCollisionMode.toNMS(fluidCollisionMode), net.minecraft.world.phys.shapes.CollisionContext.empty()), pos); // Pass block pos ++ } ++ case BLOCK_CAN_PLACE: { ++ pos = (BlockPos) params[0]; ++ org.bukkit.block.data.BlockData data = (org.bukkit.block.data.BlockData) params[1]; ++ net.minecraft.world.level.block.state.BlockState nmsData = ((org.bukkit.craftbukkit.block.data.CraftBlockData) data).getState(); ++ return nmsData.canSurvive(this, pos); ++ } ++ // Add cases for other ReadOperationType values here... ++ // case GET_ENTITIES_IN_BOX: ... (complex, needs careful list handling) ++ ++ default: ++ throw new UnsupportedOperationException("Unsupported buffered read type: " + request.type()); ++ } ++ } ++ // Leaf end - SparklyPaper - parallel world ticking ++ + public void tick(BooleanSupplier hasTimeLeft) { + this.handlingTick = true; ++ this.processAsyncReadRequests(); // Leaf - SparklyPaper - parallel world ticking + TickRateManager tickRateManager = this.tickRateManager(); + boolean runsNormally = tickRateManager.runsNormally(); + if (runsNormally) { +@@ -746,6 +870,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.advanceWeatherCycle(); + } + ++ // Leaf start - SparklyPaper - parallel world ticking ++ if (!org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ this.moonrise$midTickTasks(); ++ } else if ((++this.tickedBlocksOrFluids & 7L) != 0L) { // Keep original mid-tick logic for PWT enabled ++ this.server.moonrise$executeMidTickTasks(); ++ } ++ // Leaf end - SparklyPaper - parallel world ticking ++ + int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); + if (this.purpurConfig.playersSkipNight && this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) { // Purpur - Config for skipping night + // Paper start - create time skip event - move up calculations +@@ -1313,9 +1445,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe fluidState.tick(this, pos, blockState); } // Paper start - rewrite chunk system @@ -611,7 +774,7 @@ index a66e5f6652d9633c856490de36d8d8fdf8a5298a..60e0296312030d25f917c568c17ce86d // Paper end - rewrite chunk system } -@@ -1326,9 +1331,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1326,9 +1461,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe blockState.tick(this, pos, this.random); } // Paper start - rewrite chunk system @@ -626,7 +789,7 @@ index a66e5f6652d9633c856490de36d8d8fdf8a5298a..60e0296312030d25f917c568c17ce86d // Paper end - rewrite chunk system } -@@ -1579,6 +1587,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1579,6 +1717,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } private void addPlayer(ServerPlayer player) { @@ -635,7 +798,7 @@ index a66e5f6652d9633c856490de36d8d8fdf8a5298a..60e0296312030d25f917c568c17ce86d Entity entity = this.getEntities().get(player.getUUID()); if (entity != null) { LOGGER.warn("Force-added player with duplicate UUID {}", player.getUUID()); -@@ -1591,7 +1601,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1591,7 +1731,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // CraftBukkit start private boolean addEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { diff --git a/leaf-server/minecraft-patches/features/0135-SparklyPaper-Track-each-world-MSPT.patch b/leaf-server/minecraft-patches/features/0135-SparklyPaper-Track-each-world-MSPT.patch index aef67f40..f16beee9 100644 --- a/leaf-server/minecraft-patches/features/0135-SparklyPaper-Track-each-world-MSPT.patch +++ b/leaf-server/minecraft-patches/features/0135-SparklyPaper-Track-each-world-MSPT.patch @@ -6,10 +6,10 @@ Subject: [PATCH] SparklyPaper: Track each world MSPT Original project: https://github.com/SparklyPower/SparklyPaper diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index ac751d460ae0c8dbb858c4047c459a11b57ae175..24926aa7ed5c78b235659daf18b224b14beb744c 100644 +index 7f791ff5bdf6c29c78f863d21af16270a74d8e7e..1431a0fac3c8a846535c1bd2f60a1279d08f14ea 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -1693,7 +1693,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function threadFunction) { ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system -@@ -1088,6 +1089,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop realPlayers; // Leaves - skip @@ -178,7 +178,7 @@ index 60d5539f2a94b19281bcd8bdc2044eae1225278d..e95cbbc2757ed7f8d8aa873cec6bd492 public LevelChunk getChunkIfLoaded(int x, int z) { return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately -@@ -335,6 +345,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -336,6 +346,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); } @@ -191,7 +191,7 @@ index 60d5539f2a94b19281bcd8bdc2044eae1225278d..e95cbbc2757ed7f8d8aa873cec6bd492 @Override public final ChunkAccess moonrise$syncLoadNonFull(final int chunkX, final int chunkZ, final net.minecraft.world.level.chunk.status.ChunkStatus status) { return this.moonrise$getChunkTaskScheduler().syncLoadNonFull(chunkX, chunkZ, status); -@@ -711,6 +727,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -712,6 +728,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle this.realPlayers = Lists.newArrayList(); // Leaves - skip this.tickExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new org.dreeam.leaf.async.world.SparklyPaperServerLevelTickExecutorThreadFactory(getWorld().getName())); // SparklyPaper - parallel world ticking @@ -204,8 +204,8 @@ index 60d5539f2a94b19281bcd8bdc2044eae1225278d..e95cbbc2757ed7f8d8aa873cec6bd492 + // Leaf end - Async target finding } - // Paper start -@@ -855,12 +878,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + // Leaf start - SparklyPaper - parallel world ticking - Shutdown handling for async reads +@@ -985,12 +1008,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } this.moonrise$midTickTasks(); // Paper - rewrite chunk system // Gale end - Airplane - remove lambda from ticking guard - copied from guardEntityTick @@ -220,7 +220,7 @@ index 60d5539f2a94b19281bcd8bdc2044eae1225278d..e95cbbc2757ed7f8d8aa873cec6bd492 } // Paper - rewrite chunk system -@@ -1333,6 +1358,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1463,6 +1488,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper end - rewrite chunk system @@ -228,7 +228,7 @@ index 60d5539f2a94b19281bcd8bdc2044eae1225278d..e95cbbc2757ed7f8d8aa873cec6bd492 } private void tickBlock(BlockPos pos, Block block) { -@@ -1349,6 +1375,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1479,6 +1505,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper end - rewrite chunk system @@ -307,14 +307,14 @@ index 05d5cde42b7011091ef4ee874c0d9d5586ae3f10..88809afe30bb970a7de8bdfd26926880 this.navigation.tick(); this.customServerAiStep((ServerLevel)this.level()); diff --git a/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java b/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java -index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..22f052d02ccf22f22894a0b236af1b95c8d65e97 100644 +index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..9abb8e7b0dea2cb63dad234812d773403d0716f6 100644 --- a/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java +++ b/net/minecraft/world/entity/ai/goal/AvoidEntityGoal.java @@ -67,15 +67,24 @@ public class AvoidEntityGoal extends Goal { @Override public boolean canUse() { -+ // Leaf start - Async target finding ++ // Leaf start - Async Avoid Entity Finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { + if (!poll()) { + getNearestEntityAsync(); @@ -328,11 +328,11 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..22f052d02ccf22f22894a0b236af1b95 this.mob, this.mob.getX(), - this.mob.getY(), -+ this.mob.getEyeY(), // Leaf - Async target finding ++ this.mob.getEyeY(), // Leaf - Async Avoid Entity Finding this.mob.getZ() ); + } -+ // Leaf end - Async target finding ++ // Leaf end - Async Avoid Entity Finding if (this.toAvoid == null) { return false; } else { @@ -340,7 +340,7 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..22f052d02ccf22f22894a0b236af1b95 } } -+ // Leaf start - Async target finding ++ // Leaf start - Async Avoid Entity Finding + private boolean poll() { + if (!(this.mob.getGoalCtx().result() instanceof LivingEntity target)) return false; + var serverLevel = getServerLevel(this.mob); @@ -368,25 +368,25 @@ index 4b24bb47a53a46586a642f3fb2656b1b8b670bf2..22f052d02ccf22f22894a0b236af1b95 + z + ); + } -+ // Leaf end - Async target finding ++ // Leaf end - Async Avoid Entity Finding + @Override public boolean canContinueToUse() { return !this.pathNav.isDone(); diff --git a/net/minecraft/world/entity/ai/goal/BegGoal.java b/net/minecraft/world/entity/ai/goal/BegGoal.java -index 28ef40e8a645989ea181297069cf2bbe571f3082..1c128f60adf6a72bed0ca40b98dc579cdf9d0e21 100644 +index 28ef40e8a645989ea181297069cf2bbe571f3082..d011e4735cb8fd65a39a6b7a66386375b12aca78 100644 --- a/net/minecraft/world/entity/ai/goal/BegGoal.java +++ b/net/minecraft/world/entity/ai/goal/BegGoal.java @@ -27,8 +27,43 @@ public class BegGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.LOOK)); } -+ // Leaf start - Async target finding ++ // Leaf start - Async Target Finding + protected boolean poll() { + if (!(this.wolf.getGoalCtx().result() instanceof Player target)) return false; + if (target == null) return false; + ServerLevel serverLevel = getServerLevel(this.wolf); -+ if (serverLevel == null || !target.isAlive() || !playerHoldingInteresting0(target)) return false; ++ if (serverLevel == null || !target.isAlive() || !playerHoldingInteresting(target)) return false; + this.player = target; + return true; + } @@ -399,17 +399,17 @@ index 28ef40e8a645989ea181297069cf2bbe571f3082..1c128f60adf6a72bed0ca40b98dc579c + final TargetingConditions begTargeting = this.begTargeting; + ctx.wake = () -> { + var player = serverLevel.getNearestPlayer(begTargeting, wolf); -+ if (player != null && playerHoldingInteresting0(player)) { ++ if (player != null && playerHoldingInteresting(player)) { + return player; + } + return null; + }; + } -+ // Leaf end - Async target finding ++ // Leaf end - Async Target Finding + @Override public boolean canUse() { -+ // Leaf start - Async target finding ++ // Leaf start - Async Target Finding + if (poll()) { + return true; + } @@ -417,28 +417,23 @@ index 28ef40e8a645989ea181297069cf2bbe571f3082..1c128f60adf6a72bed0ca40b98dc579c + findTargetAsync(); + return false; + } -+ // Leaf end - Async target finding ++ // Leaf end - Async Target Finding this.player = this.level.getNearestPlayer(this.begTargeting, this.wolf); return this.player != null && this.playerHoldingInteresting(this.player); } -@@ -69,4 +104,17 @@ public class BegGoal extends Goal { - - return false; +@@ -59,10 +94,10 @@ public class BegGoal extends Goal { + this.lookTime--; } -+ -+ // Leaf start - Async target finding - static impl -+ private static boolean playerHoldingInteresting0(Player player) { -+ for (InteractionHand interactionHand : InteractionHand.values()) { -+ ItemStack itemInHand = player.getItemInHand(interactionHand); -+ if (itemInHand.is(Items.BONE) || itemInHand.is(net.minecraft.tags.ItemTags.WOLF_FOOD)) { -+ return true; -+ } -+ } -+ -+ return false; -+ } -+ // Leaf end - Async target finding - static impl - } + +- private boolean playerHoldingInteresting(Player player) { ++ private static boolean playerHoldingInteresting(Player player) { // Leaf start - Async Target Finding - static + for (InteractionHand interactionHand : InteractionHand.values()) { + ItemStack itemInHand = player.getItemInHand(interactionHand); +- if (itemInHand.is(Items.BONE) || this.wolf.isFood(itemInHand)) { ++ if (itemInHand.is(Items.BONE) || itemInHand.is(net.minecraft.tags.ItemTags.WOLF_FOOD)) { // Leaf end - Async Target Finding + return true; + } + } diff --git a/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java b/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java index 4c46cd105cde3cbcde65a02ff691c3a8edd56c76..b3205c9f35687bc37124876198ec2d657ccaa96c 100644 --- a/net/minecraft/world/entity/ai/goal/CatLieOnBedGoal.java @@ -674,7 +669,7 @@ index 3093f03d4f298bf39fec8bad2b6c22518774aea8..0a41797fd7beddce0b93d42bac6e0270 } else { this.parent = animal; diff --git a/net/minecraft/world/entity/ai/goal/GoalSelector.java b/net/minecraft/world/entity/ai/goal/GoalSelector.java -index e82e32407cec6109b9c3b0106295217f4a3f4aa2..852393af4013178562940f6a0d07e5364758ec21 100644 +index e82e32407cec6109b9c3b0106295217f4a3f4aa2..3c24382a3cced8dcea103ccc87cb506310de8461 100644 --- a/net/minecraft/world/entity/ai/goal/GoalSelector.java +++ b/net/minecraft/world/entity/ai/goal/GoalSelector.java @@ -26,13 +26,23 @@ public class GoalSelector { @@ -723,7 +718,7 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..852393af4013178562940f6a0d07e536 flagIterator ^= ca.spottedleaf.concurrentutil.util.IntegerUtil.getTrailingBit(flagIterator); // Paper end - Perf: optimize goal types if (!flag.getOrDefault(flag1, NO_GOAL).canBeReplacedBy(goal)) { -@@ -85,7 +96,136 @@ public class GoalSelector { +@@ -85,7 +96,131 @@ public class GoalSelector { return true; } @@ -748,12 +743,7 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..852393af4013178562940f6a0d07e536 + ctx.state = true; + } + -+ for (Goal.Flag flag : GOAL_FLAG_VALUES) { -+ WrappedGoal goal = this.lockedFlags.get(flag); -+ if (goal != null && !goal.isRunning()) { -+ this.lockedFlags.remove(flag); -+ } -+ } ++ this.lockedFlags.entrySet().removeIf(entry -> !entry.getValue().isRunning()); + + ctxIndex = 0; + ctx.state = true; @@ -801,7 +791,7 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..852393af4013178562940f6a0d07e536 + if (!ctx.state) { + switch (goal.getGoal()) { + case net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal t -> t.poll(); -+ case net.minecraft.world.entity.ai.goal.target.ResetUniversalAngerTargetGoal t -> t.poll(); ++ case net.minecraft.world.entity.ai.goal.target.ResetUniversalAngerTargetGoal t -> t.poll(); + default -> {} + } + } @@ -860,7 +850,7 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..852393af4013178562940f6a0d07e536 for (WrappedGoal wrappedGoal : this.availableGoals) { if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.goalTypes) || !wrappedGoal.canContinueToUse())) { // Paper - Perf: optimize goal types by removing streams wrappedGoal.stop(); -@@ -116,6 +256,24 @@ public class GoalSelector { +@@ -116,6 +251,18 @@ public class GoalSelector { } public void tickRunningGoals(boolean tickAllRunning) { @@ -872,12 +862,6 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..852393af4013178562940f6a0d07e536 + availableGoalsDirty = false; + } + ctxState = tickAllRunning ? 2 : 3; -+ } else { -+ for (WrappedGoal wrappedGoal : java.util.Objects.requireNonNull(this.ctxGoals)) { -+ if (wrappedGoal.isRunning() && (tickAllRunning || wrappedGoal.requiresUpdateEveryTick())) { -+ wrappedGoal.tick(); -+ } -+ } + } + return; + } @@ -886,7 +870,7 @@ index e82e32407cec6109b9c3b0106295217f4a3f4aa2..852393af4013178562940f6a0d07e536 if (wrappedGoal.isRunning() && (tickAllRunning || wrappedGoal.requiresUpdateEveryTick())) { wrappedGoal.tick(); diff --git a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java -index be59d0c27a83b329ec3f97c029cfb9c114e22472..5c70f33d9633326cdc59fd0812f49aa38b3d6e14 100644 +index be59d0c27a83b329ec3f97c029cfb9c114e22472..86f041ff21cf44a0cded5744055654a0bff40c42 100644 --- a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java +++ b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java @@ -20,20 +20,83 @@ public class LlamaFollowCaravanGoal extends Goal { @@ -951,7 +935,7 @@ index be59d0c27a83b329ec3f97c029cfb9c114e22472..5c70f33d9633326cdc59fd0812f49aa3 + // Leaf start - Async Target Finding + Llama llama = poll(); + double d = Double.MAX_VALUE; -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.enabled) { + if (llama == null) { + findTargetAsync(); + return false; @@ -1096,10 +1080,10 @@ index 6463c3c9b08d6058f2843c225b08a40fc30a960b..98c2b4a298ada4b02afa55f991791d86 @Override public boolean canContinueToUse() { diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..52e353ff7865cc187d83ada4e93dc955ead2a578 100644 +index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..9b8453ce2bc2cafca7c670d79b40434e7c93afca 100644 --- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -@@ -41,14 +41,67 @@ public abstract class MoveToBlockGoal extends Goal { +@@ -41,8 +41,60 @@ public abstract class MoveToBlockGoal extends Goal { this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.JUMP)); } @@ -1147,7 +1131,6 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..52e353ff7865cc187d83ada4e93dc955 + Strider, + TurtleToWater, + TurtleLay, -+ Unknown, + } + // Leaf end - Async search block + @@ -1161,32 +1144,20 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..52e353ff7865cc187d83ada4e93dc955 if (this.nextStartTick > 0) { this.nextStartTick--; return false; - } else { - this.nextStartTick = this.nextStartTick(this.mob); -- return this.findNearestBlock(); -+ return this.findNearestBlockAsync(); // Leaf - Async search block - } +@@ -109,6 +161,12 @@ public abstract class MoveToBlockGoal extends Goal { } -@@ -108,6 +161,17 @@ public abstract class MoveToBlockGoal extends Goal { - return this.reachedTarget; - } - -+ // Leaf start - Async search block -+ protected boolean findNearestBlockAsync() { -+ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchBlock -+ && this.typeToCheck() != TypeToCheck.Unknown) { + protected boolean findNearestBlock() { ++ // Leaf start - Async search block ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchBlock) { + getBlockAsync(); + return false; + } -+ return findNearestBlock(); -+ } -+ // Leaf end - Async search block -+ - protected boolean findNearestBlock() { ++ // Leaf end - Async search block int i = this.searchRange; int i1 = this.verticalSearchRange; -@@ -133,5 +197,108 @@ public abstract class MoveToBlockGoal extends Goal { + BlockPos blockPos = this.mob.blockPosition(); +@@ -133,5 +191,105 @@ public abstract class MoveToBlockGoal extends Goal { return false; } @@ -1229,9 +1200,7 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..52e353ff7865cc187d83ada4e93dc955 protected abstract boolean isValidTarget(LevelReader level, BlockPos pos); + + // Leaf start - Async search block -+ protected TypeToCheck typeToCheck() { -+ return TypeToCheck.Unknown; -+ } ++ protected abstract TypeToCheck typeToCheck(); + + private static boolean isValidTargetAsync( + TypeToCheck type, @@ -1289,7 +1258,6 @@ index 3f080b15543bf8c5fa0774b62d7f12e13b82511a..52e353ff7865cc187d83ada4e93dc955 + case TurtleLay -> { + return level.isEmptyBlock(pos.above()) && net.minecraft.world.level.block.TurtleEggBlock.isSand(level, pos); + } -+ case Unknown -> throw new IllegalStateException(); + case null -> throw new IllegalStateException(); + } + // Leaf end - Async search block @@ -1359,10 +1327,10 @@ index 3c274d917bca9de87abfb842f5f332e112a7a2d7..2491b84641443ecfb8afc3b179e1cf80 public boolean canContinueToUse() { return this.tick > 0; diff --git a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java -index c67a88c9c77ece7c85ffb169ac96da4f28291228..93bf51c9299354be2a581618bd31d2c4cc7c1d30 100644 +index c67a88c9c77ece7c85ffb169ac96da4f28291228..14d9b492ba431d534e0c6a567d0b7700b4c8a02d 100644 --- a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java +++ b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java -@@ -37,10 +37,17 @@ public class RemoveBlockGoal extends MoveToBlockGoal { +@@ -37,7 +37,14 @@ public class RemoveBlockGoal extends MoveToBlockGoal { public boolean canUse() { if (!getServerLevel(this.removerMob).purpurConfig.zombieBypassMobGriefing == !getServerLevel(this.removerMob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected return false; @@ -1377,11 +1345,7 @@ index c67a88c9c77ece7c85ffb169ac96da4f28291228..93bf51c9299354be2a581618bd31d2c4 + if (this.nextStartTick > 0) { this.nextStartTick--; return false; -- } else if (this.findNearestBlock()) { -+ } else if (this.findNearestBlockAsync()) { // Leaf - async search block - this.nextStartTick = reducedTickDelay(20); - return true; - } else { + } else if (this.findNearestBlock()) { @@ -151,8 +158,15 @@ public class RemoveBlockGoal extends MoveToBlockGoal { protected boolean isValidTarget(LevelReader level, BlockPos pos) { ChunkAccess chunk = level.getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4); // Paper - Prevent AI rules from loading chunks @@ -1400,10 +1364,10 @@ index c67a88c9c77ece7c85ffb169ac96da4f28291228..93bf51c9299354be2a581618bd31d2c4 + // Leaf end - Async search block } diff --git a/net/minecraft/world/entity/ai/goal/TemptGoal.java b/net/minecraft/world/entity/ai/goal/TemptGoal.java -index f88f618d34fb343b31de3af1a875d6633703df71..22656c9ef08a65e23cabdd9f6af84f3d2279b075 100644 +index f88f618d34fb343b31de3af1a875d6633703df71..754c379b42cf65c1d2278b474cdfbe50e9e62b34 100644 --- a/net/minecraft/world/entity/ai/goal/TemptGoal.java +++ b/net/minecraft/world/entity/ai/goal/TemptGoal.java -@@ -36,14 +36,43 @@ public class TemptGoal extends Goal { +@@ -36,12 +36,51 @@ public class TemptGoal extends Goal { this.targetingConditions = TEMPT_TARGETING.copy().selector((entity, level) -> this.shouldFollow(entity)); } @@ -1433,22 +1397,28 @@ index f88f618d34fb343b31de3af1a875d6633703df71..22656c9ef08a65e23cabdd9f6af84f3d this.calmDown--; return false; } else { -- this.player = getServerLevel(this.mob) -- .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); + // Leaf start - Async Tempt Finding + if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { -+ if (!poll()) { ++ if (poll()) { ++ if (this.player != null) { ++ org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT); ++ if (event.isCancelled()) { ++ return false; ++ } ++ this.player = (event.getTarget() == null) ? null : ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle(); ++ } ++ if (this.player != null) { ++ return true; ++ } ++ } else { + getNearestPlayerAsync(); + return false; + } -+ } else { -+ this.player = getServerLevel(this.mob) -+ .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); + } + // Leaf end - Async Tempt Finding + this.player = getServerLevel(this.mob) + .getNearestPlayer(this.targetingConditions.range(this.mob.getAttributeValue(Attributes.TEMPT_RANGE)), this.mob); // CraftBukkit start - if (this.player != null) { - org.bukkit.event.entity.EntityTargetLivingEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TEMPT); diff --git a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java index 4644f3f7af89623ca6218c0dd24bb6cd67db553b..c9750ad322ddaa9c457f0e652d87c7abf8559358 100644 --- a/net/minecraft/world/entity/ai/goal/target/DefendVillageTargetGoal.java @@ -1628,10 +1598,10 @@ index 25fe78116ce01eeefe5c958423734195d27302eb..e306c1cfc44878ea130d8046b31cf617 mob.setTarget(target, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY, true); // CraftBukkit - reason } diff --git a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -index 85eae0a14f7a417dfd8c911079d05354a98e5834..745ef9ea16171d8dad3082878f8883031bd79a84 100644 +index 85eae0a14f7a417dfd8c911079d05354a98e5834..f59d5c9be0eb10f5b5192442e1850900d71a31e9 100644 --- a/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java -@@ -41,12 +41,52 @@ public class NearestAttackableTargetGoal extends TargetG +@@ -41,8 +41,43 @@ public class NearestAttackableTargetGoal extends TargetG this.targetConditions = TargetingConditions.forCombat().range(this.getFollowDistance()).selector(selector); } @@ -1644,12 +1614,7 @@ index 85eae0a14f7a417dfd8c911079d05354a98e5834..745ef9ea16171d8dad3082878f888303 + return true; + } + -+ protected void findTargetAsync() { -+ if (!org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { -+ this.findTarget(); -+ return; -+ } -+ ++ private void findTargetAsync() { + final Mob mob = this.mob; + final var ctx = mob.getGoalCtx(); + if (!ctx.state) return; @@ -1680,16 +1645,27 @@ index 85eae0a14f7a417dfd8c911079d05354a98e5834..745ef9ea16171d8dad3082878f888303 if (this.randomInterval > 0 && this.mob.getRandom().nextInt(this.randomInterval) != 0) { return false; } else { -- this.findTarget(); -+ this.findTargetAsync(); // Leaf - Async target finding - return this.target != null; - } - } +@@ -57,6 +92,15 @@ public class NearestAttackableTargetGoal extends TargetG + + protected void findTarget() { + ServerLevel serverLevel = getServerLevel(this.mob); ++ ++ // Leaf start - Async Target Finding ++ if (org.dreeam.leaf.config.modules.async.AsyncTargetFinding.searchEntity) { ++ this.findTargetAsync(); ++ this.target = null; ++ return; ++ } ++ // Leaf end - Async Target Finding ++ + if (this.targetType != Player.class && this.targetType != ServerPlayer.class) { + this.target = serverLevel.getNearestEntity( + this.mob.level().getEntitiesOfClass(this.targetType, this.getTargetSearchArea(this.getFollowDistance()), entity -> true), diff --git a/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java -index 4604a603c4ddd0a9242e859aaa5a511c2d4c4f84..84c7b89e7c894c0f544cf0ffcf9dff3f6a5919cc 100644 +index 4604a603c4ddd0a9242e859aaa5a511c2d4c4f84..7274dfb52ca4a08cdebcd04294cedc73460593e5 100644 --- a/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java +++ b/net/minecraft/world/entity/ai/goal/target/NearestHealableRaiderTargetGoal.java -@@ -23,12 +23,20 @@ public class NearestHealableRaiderTargetGoal extends Nea +@@ -23,7 +23,15 @@ public class NearestHealableRaiderTargetGoal extends Nea @Override public boolean canUse() { @@ -1706,12 +1682,6 @@ index 4604a603c4ddd0a9242e859aaa5a511c2d4c4f84..84c7b89e7c894c0f544cf0ffcf9dff3f return false; } else if (!((Raider)this.mob).hasActiveRaid()) { return false; - } else { -- this.findTarget(); -+ this.findTargetAsync(); // Leaf - Async target finding - return this.target != null; - } - } diff --git a/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java b/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java index abf57494950f55bbd75f335f26736cb9e703c197..efd2418f56c36e7850edde483a2a4906dd622441 100644 --- a/net/minecraft/world/entity/ai/goal/target/NonTameRandomTargetGoal.java diff --git a/leaf-server/minecraft-patches/features/0163-Protocol-Core.patch b/leaf-server/minecraft-patches/features/0163-Protocol-Core.patch index 67e20d21..3aa9a319 100644 --- a/leaf-server/minecraft-patches/features/0163-Protocol-Core.patch +++ b/leaf-server/minecraft-patches/features/0163-Protocol-Core.patch @@ -22,10 +22,10 @@ index 7e19dfe90a63ff26f03b95891dacb7360bba5a3c..5d0961f06c23121883c4f0b889ccdf32 } diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 98af1ad020a003db66d7319f33d43deec315aec5..9669036e6b7f1830888e48c99acb01d443f4e9f0 100644 +index c92005fcb9c6fdf63bf0854122d59d130315db85..e3194caff68499028d61d5755edce29b5c23253f 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -1839,6 +1839,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements ca.spottedleaf.moonrise.patch - return ""; - } else { - Property property = propertyEntry.getKey(); -- return property.getName() + "=" + this.getName(property, propertyEntry.getValue()); -+ return property.getName() + "=" + this.getName(property, propertyEntry.getValue()); // Leaf - paw optimization - diff on change - } - } - -@@ -73,13 +73,37 @@ public abstract class StateHolder implements ca.spottedleaf.moonrise.patch - stringBuilder.append(this.owner); - if (!this.getValues().isEmpty()) { - stringBuilder.append('['); -- stringBuilder.append(this.getValues().entrySet().stream().map(PROPERTY_ENTRY_TO_STRING_FUNCTION).collect(Collectors.joining(","))); -+ // Leaf start - paw optimization -+ int i = 0; -+ for (Map.Entry, Comparable> propertyEntry : this.getValues().entrySet()) { -+ if (propertyEntry == null) { -+ stringBuilder.append(""); -+ } else { -+ Property property = propertyEntry.getKey(); -+ Comparable value = propertyEntry.getValue(); -+ -+ stringBuilder.append(property.getName()).append("=").append(getValueName(property, value)); -+ } -+ -+ if (i < this.getValues().size() - 1) { -+ stringBuilder.append(","); -+ } -+ -+ i++; -+ } -+ // Leaf end - paw optimization - stringBuilder.append(']'); - } - - return stringBuilder.toString(); - } - -+ // Leaf start - paw optimization -+ private > String getValueName(Property property, Comparable value) { -+ return property.getName((T) value); -+ } -+ // Leaf end - paw optimization -+ - public Collection> getProperties() { - return this.optimisedTable.getProperties(); // Paper - optimise blockstate property access - } diff --git a/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java b/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java index 03ec2264b19e1794b609fe09d1ceaba4e0c4d669..3f38fe0140d13c7c356340ba06b55469ede0a1ad 100644 --- a/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java diff --git a/leaf-server/paper-patches/features/0030-SparklyPaper-Parallel-world-ticking.patch b/leaf-server/paper-patches/features/0030-SparklyPaper-Parallel-world-ticking.patch index 2defa582..32ce99bb 100644 --- a/leaf-server/paper-patches/features/0030-SparklyPaper-Parallel-world-ticking.patch +++ b/leaf-server/paper-patches/features/0030-SparklyPaper-Parallel-world-ticking.patch @@ -255,7 +255,7 @@ index 548fcd9646dee0c40b6ba9b3dafb9ca157dfe324..d7af94890bfccd6ff665d920cecfa1e5 } else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) { // Leaf start - Multithreaded tracker diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index af33cab59932f4ec135caf94dc5828930833daf6..92463ddc6fdcf542ce4a6d2a5059d4a9f7a6085a 100644 +index af33cab59932f4ec135caf94dc5828930833daf6..caa92e48d031cb54950e6613a82f407d7ed2455a 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -455,7 +455,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { @@ -313,16 +313,7 @@ index af33cab59932f4ec135caf94dc5828930833daf6..92463ddc6fdcf542ce4a6d2a5059d4a9 net.minecraft.world.level.Level.ExplosionInteraction explosionType; if (!breakBlocks) { explosionType = net.minecraft.world.level.Level.ExplosionInteraction.NONE; // Don't break blocks -@@ -956,6 +972,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x >> 4, z >> 4, "Cannot retrieve chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) - warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper - // Transient load for this tick - return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z); -@@ -986,6 +1004,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -986,6 +1002,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void setBiome(int x, int y, int z, Holder bb) { BlockPos pos = new BlockPos(x, 0, z); @@ -331,7 +322,7 @@ index af33cab59932f4ec135caf94dc5828930833daf6..92463ddc6fdcf542ce4a6d2a5059d4a9 if (this.world.hasChunkAt(pos)) { net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkAt(pos); -@@ -2328,6 +2348,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -2328,6 +2346,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { @@ -341,22 +332,124 @@ index af33cab59932f4ec135caf94dc5828930833daf6..92463ddc6fdcf542ce4a6d2a5059d4a9 } // Paper end diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index 811823a1a7e24a19a7e37eb4c08efdfa19e839ed..b94efcab12d41fa8745e5bb55cfa6481d8262e74 100644 +index 811823a1a7e24a19a7e37eb4c08efdfa19e839ed..ac45c5cbe547705e3e341011740cf911c39f80c0 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -75,6 +75,11 @@ public class CraftBlock implements Block { +@@ -74,13 +74,98 @@ public class CraftBlock implements Block { + return new CraftBlock(world, position); } - public net.minecraft.world.level.block.state.BlockState getNMS() { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); ++ // Leaf start - SparklyPaper - parallel world ticking ++ private ServerLevel getServerLevel() { ++ return (this.world instanceof ServerLevel serverLevel) ? serverLevel : null; ++ } ++ ++ private boolean needsBuffering(ServerLevel level, String handlingMode) { ++ // No ServerLevel means no queue, can't buffer ++ return level != null && org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && handlingMode.equals("BUFFERED") && !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(level); ++ } ++ ++ private void checkStrictMode(ServerLevel level, String handlingMode, String methodName) { ++ // Only check if PWT enabled, mode is STRICT, and we have a ServerLevel ++ if (level != null && org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && handlingMode.equals("STRICT")) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, this.position, "PWT: Async unsafe block read (strict mode): " + methodName); + } -+ // SparklyPaper end - parallel world ticking - return this.world.getBlockState(this.position); ++ } ++ ++ private T executeBufferedRead(ServerLevel level, org.dreeam.leaf.async.world.ReadOperationType type, Object[] params, T defaultValue, String methodName) { ++ if (level == null) { // Should not happen if called correctly after needsBuffering ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: executeBufferedRead called with null ServerLevel for " + methodName); ++ return defaultValue; ++ } ++ ++ java.util.concurrent.CompletableFuture future = new java.util.concurrent.CompletableFuture<>(); ++ ++ // Leaf start - SparklyPaper - parallel world ticking - Shutdown handling for async reads ++ if (level.isShuttingDown()) { ++ future.completeExceptionally(new IllegalStateException("World " + level.getWorld().getName() + " is shutting down. Cannot queue new buffered read: " + type)); ++ } else { ++ org.dreeam.leaf.async.world.WorldReadRequest request = new org.dreeam.leaf.async.world.WorldReadRequest(type, params, future); ++ level.asyncReadRequestQueue.offer(request); // Assumes queue exists on ServerLevel ++ } ++ // Leaf end - SparklyPaper - parallel world ticking - Shutdown handling for async reads ++ ++ try { ++ Object result = future.join(); // Block until tick thread completes it ++ if (result == null) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Buffered async read returned null for " + methodName + " - returning default."); ++ return defaultValue; ++ } ++ return (T) result; ++ } catch (java.util.concurrent.CompletionException e) { ++ // Leaf start - SparklyPaper - parallel world ticking - Shutdown handling for async reads ++ if (e.getCause() instanceof IllegalStateException && e.getCause().getMessage() != null && e.getCause().getMessage().contains("shutting down")) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Async block read for " + methodName + " cancelled due to world shutdown: " + e.getCause().getMessage()); ++ } else { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Async block read failed for " + methodName + " on tick thread.", e.getCause() != null ? e.getCause() : e); ++ } ++ return defaultValue; // Return default or rethrow if appropriate for your error handling strategy ++ // Leaf end - SparklyPaper - parallel world ticking - Shutdown handling for async reads ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Unexpected error during async block read for " + methodName, e); ++ return defaultValue; ++ } ++ } ++ // Leaf end - SparklyPaper - parallel world ticking ++ + public net.minecraft.world.level.block.state.BlockState getNMS() { +- return this.world.getBlockState(this.position); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ ++ if (needsBuffering(level, handlingMode)) { ++ // Buffered path ++ return executeBufferedRead(level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_GET_NMS_STATE, new Object[]{this.position}, Blocks.AIR.defaultBlockState(), "getNMS"); ++ } else { ++ // Strict/Disabled/Non-ServerLevel path ++ checkStrictMode(level, handlingMode, "getNMS"); ++ try { ++ return this.world.getBlockState(this.position); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getNMS" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return Blocks.AIR.defaultBlockState(); ++ } ++ } ++ // Leaf end - SparklyPaper - parallel world ticking } -@@ -157,6 +162,11 @@ public class CraftBlock implements Block { + // Paper start + public net.minecraft.world.level.material.FluidState getNMSFluid() { +- return this.world.getFluidState(this.position); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "getNMSFluid"); ++ ++ try { ++ return this.world.getFluidState(this.position); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getNMSFluid" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return net.minecraft.world.level.material.Fluids.EMPTY.defaultFluidState(); ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + // Paper end + +@@ -144,10 +229,12 @@ public class CraftBlock implements Block { + return this.getWorld().getChunkAt(this); + } + ++ @Deprecated // Leaf - SparklyPaper - parallel world ticking - Magic value + public void setData(final byte data) { + this.setData(data, 3); + } + ++ @Deprecated // Leaf - SparklyPaper - parallel world ticking - Magic value + public void setData(final byte data, boolean applyPhysics) { + if (applyPhysics) { + this.setData(data, 3); +@@ -157,12 +244,18 @@ public class CraftBlock implements Block { } private void setData(final byte data, int flag) { @@ -368,7 +461,23 @@ index 811823a1a7e24a19a7e37eb4c08efdfa19e839ed..b94efcab12d41fa8745e5bb55cfa6481 this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flag); } -@@ -198,6 +208,12 @@ public class CraftBlock implements Block { + @Override ++ @Deprecated // Leaf - SparklyPaper - parallel world ticking - Magic value + public byte getData() { +- net.minecraft.world.level.block.state.BlockState blockData = this.world.getBlockState(this.position); ++ net.minecraft.world.level.block.state.BlockState blockData = this.getNMS(); // Leaf - SparklyPaper - parallel world ticking + return CraftMagicNumbers.toLegacyData(blockData); + } + +@@ -179,6 +272,7 @@ public class CraftBlock implements Block { + @Override + public void setType(Material type, boolean applyPhysics) { + Preconditions.checkArgument(type != null, "Material cannot be null"); ++ // Leaf - SparklyPaper - parallel world ticking - Delegates to setBlockData, which delegates to setTypeAndData (which has checks) + this.setBlockData(type.createBlockData(), applyPhysics); + } + +@@ -198,6 +292,12 @@ public class CraftBlock implements Block { } public static boolean setTypeAndData(LevelAccessor world, BlockPos position, net.minecraft.world.level.block.state.BlockState old, net.minecraft.world.level.block.state.BlockState blockData, boolean applyPhysics) { @@ -381,27 +490,205 @@ index 811823a1a7e24a19a7e37eb4c08efdfa19e839ed..b94efcab12d41fa8745e5bb55cfa6481 // SPIGOT-611: need to do this to prevent glitchiness. Easier to handle this here (like /setblock) than to fix weirdness in tile entity cleanup if (old.hasBlockEntity() && blockData.getBlock() != old.getBlock()) { // SPIGOT-3725 remove old tile entity if block changes // SPIGOT-4612: faster - just clear tile -@@ -343,18 +359,33 @@ public class CraftBlock implements Block { +@@ -226,22 +326,62 @@ public class CraftBlock implements Block { + + @Override + public Material getType() { +- return this.world.getBlockState(this.position).getBukkitMaterial(); // Paper - optimise getType calls ++ return this.getNMS().getBukkitMaterial(); // Paper - optimise getType calls // Leaf - SparklyPaper - parallel world ticking + } + + @Override + public byte getLightLevel() { +- return (byte) this.world.getMinecraftWorld().getMaxLocalRawBrightness(this.position); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "getLightLevel"); // Strict check if applicable ++ ++ try { ++ // Requires Level for getMaxLocalRawBrightness ++ if (this.world instanceof net.minecraft.world.level.Level nmsLevel) { ++ return (byte) nmsLevel.getMaxLocalRawBrightness(this.position); ++ } ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: getLightLevel called on non-Level - returning 0"); ++ return 0; ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getLightLevel" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return 0; ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override + public byte getLightFromSky() { +- return (byte) this.world.getBrightness(LightLayer.SKY, this.position); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "getLightFromSky"); // Strict check if applicable ++ ++ try { ++ // LevelAccessor has getBrightness ++ return (byte) this.world.getBrightness(LightLayer.SKY, this.position); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getLightFromSky" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return 0; ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override + public byte getLightFromBlocks() { +- return (byte) this.world.getBrightness(LightLayer.BLOCK, this.position); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "getLightFromBlocks"); // Strict check if applicable ++ ++ try { ++ // LevelAccessor has getBrightness ++ return (byte) this.world.getBrightness(LightLayer.BLOCK, this.position); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getLightFromBlocks" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return 0; ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + public Block getFace(final BlockFace face) { +@@ -282,51 +422,37 @@ public class CraftBlock implements Block { + + @Override + public String toString() { +- return "CraftBlock{pos=" + this.position + ",type=" + this.getType() + ",data=" + this.getNMS() + ",fluid=" + this.world.getFluidState(this.position) + '}'; ++ return "CraftBlock{pos=" + this.position + ",type=" + this.getType() + ",data=" + this.getNMS() + ",fluid=" + this.getNMSFluid() + '}'; // Leaf - SparklyPaper - parallel world ticking + } + + public static BlockFace notchToBlockFace(Direction notch) { +- if (notch == null) { +- return BlockFace.SELF; +- } +- switch (notch) { +- case DOWN: +- return BlockFace.DOWN; +- case UP: +- return BlockFace.UP; +- case NORTH: +- return BlockFace.NORTH; +- case SOUTH: +- return BlockFace.SOUTH; +- case WEST: +- return BlockFace.WEST; +- case EAST: +- return BlockFace.EAST; +- default: +- return BlockFace.SELF; +- } ++ // Leaf start - SparklyPaper - parallel world ticking - formatting ++ if (notch == null) return BlockFace.SELF; ++ return switch (notch) { ++ case DOWN -> BlockFace.DOWN; ++ case UP -> BlockFace.UP; ++ case NORTH -> BlockFace.NORTH; ++ case SOUTH -> BlockFace.SOUTH; ++ case WEST -> BlockFace.WEST; ++ case EAST -> BlockFace.EAST; ++ default -> BlockFace.SELF; ++ }; ++ // Leaf end - SparklyPaper - parallel world ticking - formatting + } + + public static Direction blockFaceToNotch(BlockFace face) { +- if (face == null) { +- return null; +- } +- switch (face) { +- case DOWN: +- return Direction.DOWN; +- case UP: +- return Direction.UP; +- case NORTH: +- return Direction.NORTH; +- case SOUTH: +- return Direction.SOUTH; +- case WEST: +- return Direction.WEST; +- case EAST: +- return Direction.EAST; +- default: +- return null; +- } ++ // Leaf start - SparklyPaper - parallel world ticking - formatting ++ if (face == null) return null; ++ return switch (face) { ++ case DOWN -> Direction.DOWN; ++ case UP -> Direction.UP; ++ case NORTH -> Direction.NORTH; ++ case SOUTH -> Direction.SOUTH; ++ case WEST -> Direction.WEST; ++ case EAST -> Direction.EAST; ++ default -> null; ++ }; ++ // Leaf end - SparklyPaper - parallel world ticking - formatting + } + + @Override +@@ -343,18 +469,65 @@ public class CraftBlock implements Block { @Override public Biome getBiome() { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); +- return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ ++ if (needsBuffering(level, handlingMode)) { ++ // Buffered path ++ net.minecraft.core.Holder nmsResult = executeBufferedRead( ++ level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_GET_BIOME, new Object[]{this.position}, null, "getBiome" ++ ); ++ return (nmsResult != null) ? CraftBiome.minecraftHolderToBukkit(nmsResult) : Biome.PLAINS; // Default biome ++ } else { ++ // Strict/Disabled/Non-ServerLevel path ++ checkStrictMode(level, handlingMode, "getBiome"); ++ try { ++ // Use Bukkit API, which should delegate safely (or buffer itself if needed) ++ return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getBiome" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return Biome.PLAINS; // Default biome on error ++ } + } -+ // SparklyPaper end - parallel world ticking - return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); ++ // Leaf end - SparklyPaper - parallel world ticking } // Paper start @Override public Biome getComputedBiome() { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); +- return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ()); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ ++ if (needsBuffering(level, handlingMode)) { ++ // Buffered path ++ net.minecraft.core.Holder nmsResult = executeBufferedRead( ++ level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_GET_COMPUTED_BIOME, new Object[]{this.position}, null, "getComputedBiome" ++ ); ++ return (nmsResult != null) ? CraftBiome.minecraftHolderToBukkit(nmsResult) : Biome.PLAINS; // Default biome ++ } else { ++ // Strict/Disabled/Non-ServerLevel path ++ checkStrictMode(level, handlingMode, "getComputedBiome"); ++ try { ++ // Use Bukkit API ++ return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ()); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getComputedBiome" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return Biome.PLAINS; // Default biome on error ++ } + } -+ // SparklyPaper end - parallel world ticking - return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ()); ++ // Leaf end - SparklyPaper - parallel world ticking } // Paper end @@ -415,101 +702,756 @@ index 811823a1a7e24a19a7e37eb4c08efdfa19e839ed..b94efcab12d41fa8745e5bb55cfa6481 this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio); } -@@ -375,6 +406,11 @@ public class CraftBlock implements Block { +@@ -370,12 +543,50 @@ public class CraftBlock implements Block { + + @Override + public boolean isBlockPowered() { +- return this.world.getMinecraftWorld().getDirectSignalTo(this.position) > 0; ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "isBlockPowered"); // Strict check if applicable ++ ++ try { ++ // Requires Level.getDirectSignalTo ++ if (this.world instanceof net.minecraft.world.level.Level nmsLevel) { ++ return nmsLevel.getDirectSignalTo(this.position) > 0; ++ } ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: isBlockPowered called on non-Level - returning false"); ++ return false; ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for isBlockPowered" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return false; ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } @Override public boolean isBlockIndirectlyPowered() { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); +- return this.world.getMinecraftWorld().hasNeighborSignal(this.position); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ ++ if (needsBuffering(level, handlingMode)) { ++ // Buffered path ++ return executeBufferedRead(level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_IS_INDIRECTLY_POWERED, new Object[]{this.position}, false, "isBlockIndirectlyPowered"); ++ } else { ++ // Strict/Disabled/Non-ServerLevel path ++ checkStrictMode(level, handlingMode, "isBlockIndirectlyPowered"); ++ try { ++ // Requires Level.hasNeighborSignal ++ if (this.world instanceof net.minecraft.world.level.Level nmsLevel) { ++ return nmsLevel.hasNeighborSignal(this.position); ++ } ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: isBlockIndirectlyPowered called on non-Level - returning false"); ++ return false; ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for isBlockIndirectlyPowered" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return false; ++ } + } -+ // SparklyPaper end - parallel world ticking - return this.world.getMinecraftWorld().hasNeighborSignal(this.position); ++ // Leaf end - SparklyPaper - parallel world ticking } -@@ -414,6 +450,11 @@ public class CraftBlock implements Block { + @Override +@@ -397,46 +608,102 @@ public class CraftBlock implements Block { @Override - public int getBlockPower(BlockFace face) { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + public boolean isBlockFacePowered(BlockFace face) { +- return this.world.getMinecraftWorld().hasSignal(this.position, CraftBlock.blockFaceToNotch(face)); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "isBlockFacePowered"); // Strict check if applicable ++ ++ try { ++ if (this.world instanceof net.minecraft.world.level.Level nmsLevel) { ++ return nmsLevel.hasSignal(this.position, CraftBlock.blockFaceToNotch(face)); ++ } ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: isBlockFacePowered called on non-Level - returning false"); ++ return false; ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for isBlockFacePowered" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return false; + } -+ // SparklyPaper end - parallel world ticking - int power = 0; - net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); - int x = this.getX(); -@@ -484,6 +525,11 @@ public class CraftBlock implements Block { ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override + public boolean isBlockFaceIndirectlyPowered(BlockFace face) { +- int power = this.world.getMinecraftWorld().getSignal(this.position, CraftBlock.blockFaceToNotch(face)); +- +- Block relative = this.getRelative(face); +- if (relative.getType() == Material.REDSTONE_WIRE) { +- return Math.max(power, relative.getData()) > 0; ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "isBlockFaceIndirectlyPowered"); // Strict check if applicable ++ ++ try { ++ // Requires Level.getSignal and potentially relative block access (which might need safety checks) ++ if (this.world instanceof net.minecraft.world.level.Level nmsLevel) { ++ int power = nmsLevel.getSignal(this.position, CraftBlock.blockFaceToNotch(face)); ++ Block relative = this.getRelative(face); // getRelative delegates, safety depends on target block ++ if (relative.getType() == Material.REDSTONE_WIRE) { ++ // getData might need buffering if called on relative block ++ return Math.max(power, relative.getData()) > 0; ++ } ++ return power > 0; ++ } ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: isBlockFaceIndirectlyPowered called on non-Level - returning false"); ++ return false; ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for isBlockFaceIndirectlyPowered" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return false; + } +- +- return power > 0; ++ // Leaf end - SparklyPaper - parallel world ticking + } + ++ // Leaf start - SparklyPaper - parallel world ticking + @Override + public int getBlockPower(BlockFace face) { +- int power = 0; +- net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); +- int x = this.getX(); +- int y = this.getY(); +- int z = this.getZ(); +- if ((face == BlockFace.DOWN || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x, y - 1, z), Direction.DOWN)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x, y - 1, z))); +- if ((face == BlockFace.UP || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x, y + 1, z), Direction.UP)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x, y + 1, z))); +- if ((face == BlockFace.EAST || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x + 1, y, z), Direction.EAST)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x + 1, y, z))); +- if ((face == BlockFace.WEST || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x - 1, y, z), Direction.WEST)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x - 1, y, z))); +- if ((face == BlockFace.NORTH || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x, y, z - 1), Direction.NORTH)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x, y, z - 1))); +- if ((face == BlockFace.SOUTH || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x, y, z + 1), Direction.SOUTH)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x, y, z + 1))); +- return power > 0 ? power : (face == BlockFace.SELF ? this.isBlockIndirectlyPowered() : this.isBlockFaceIndirectlyPowered(face)) ? 15 : 0; +- } +- +- private static int getPower(int i, net.minecraft.world.level.block.state.BlockState iblockdata) { +- if (!iblockdata.is(Blocks.REDSTONE_WIRE)) { +- return i; ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ ++ if (needsBuffering(level, handlingMode)) { ++ // Buffered path ++ return executeBufferedRead(level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_GET_BLOCK_POWER, new Object[]{this.position, face}, 0, "getBlockPower"); + } else { +- int j = iblockdata.getValue(RedStoneWireBlock.POWER); ++ // Strict/Disabled/Non-ServerLevel path ++ checkStrictMode(level, handlingMode, "getBlockPower"); ++ try { ++ // Requires Level for hasSignal and getBlockState ++ if (this.world instanceof net.minecraft.world.level.Level nmsLevel) { ++ int power = 0; ++ int x = this.getX(); int y = this.getY(); int z = this.getZ(); ++ BlockPos currentPos = this.position; // Use immutable position ++ ++ // Check neighbors using relative positions ++ if ((face == BlockFace.DOWN || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.below(), Direction.DOWN)) power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.below())); ++ if ((face == BlockFace.UP || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.above(), Direction.UP)) power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.above())); ++ if ((face == BlockFace.EAST || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.east(), Direction.EAST)) power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.east())); ++ if ((face == BlockFace.WEST || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.west(), Direction.WEST)) power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.west())); ++ if ((face == BlockFace.NORTH || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.north(), Direction.NORTH)) power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.north())); ++ if ((face == BlockFace.SOUTH || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.south(), Direction.SOUTH)) power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.south())); ++ ++ // Need to call isBlockIndirectlyPowered/isBlockFaceIndirectlyPowered safely ++ boolean indirect = (face == BlockFace.SELF) ? this.isBlockIndirectlyPowered() : this.isBlockFaceIndirectlyPowered(face); ++ return power > 0 ? power : (indirect ? 15 : 0); ++ } else { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: getBlockPower called on non-Level - returning 0"); ++ return 0; ++ } ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getBlockPower" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return 0; ++ } ++ } ++ } + +- return j > i ? j : i; ++ // Static helper, safe ++ public static int getPower(int currentMax, net.minecraft.world.level.block.state.BlockState neighborState) { ++ if (!neighborState.is(Blocks.REDSTONE_WIRE)) { ++ return currentMax; ++ } else { ++ int neighborPower = neighborState.getValue(RedStoneWireBlock.POWER); ++ return Math.max(neighborPower, currentMax); + } + } ++ // Leaf end - SparklyPaper - parallel world ticking + + @Override + public int getBlockPower() { +@@ -479,105 +746,179 @@ public class CraftBlock implements Block { + + @Override + public PistonMoveReaction getPistonMoveReaction() { ++ // Leaf - SparklyPaper - parallel world ticking - Uses safe getNMS() + return PistonMoveReaction.getById(this.getNMS().getPistonPushReaction().ordinal()); + } @Override public boolean breakNaturally() { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); +- return this.breakNaturally(null); ++ // Leaf start - SparklyPaper - parallel world ticking - Write operation check ++ ServerLevel level = getServerLevel(); ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ if (level != null) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, position, "PWT: Cannot modify world asynchronously (breakNaturally)"); ++ } else { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Attempted breakNaturally on non-ServerLevel - safety not guaranteed."); ++ } + } -+ // SparklyPaper end - parallel world ticking - return this.breakNaturally(null); ++ // Delegates to overload ++ return this.breakNaturally(null, true, true); // Default to dropping XP and triggering effects ++ // Leaf end - SparklyPaper - parallel world ticking - Write operation check } -@@ -543,6 +589,11 @@ public class CraftBlock implements Block { + @Override + public boolean breakNaturally(ItemStack item) { + // Paper start +- return this.breakNaturally(item, false); ++ return this.breakNaturally(item, true, true); // Leaf - SparklyPaper - parallel world ticking - Delegates to full overload + } + + @Override + public boolean breakNaturally(boolean triggerEffect, boolean dropExperience) { +- return this.breakNaturally(null, triggerEffect, dropExperience); ++ return this.breakNaturally(null, triggerEffect, dropExperience);// Leaf - SparklyPaper - parallel world ticking - Delegates to full overload + } + + @Override + public boolean breakNaturally(ItemStack item, boolean triggerEffect, boolean dropExperience) { + // Paper end + // Order matters here, need to drop before setting to air so skulls can get their data ++ // Leaf start - SparklyPaper - parallel world ticking - Write operation check ++ // (already done in simpler overload, but check again for safety) ++ ServerLevel level = getServerLevel(); ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ if (level != null) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, position, "PWT: Cannot modify world asynchronously (breakNaturally item...)"); ++ } else { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Attempted breakNaturally(item...) on non-ServerLevel - safety not guaranteed."); ++ } ++ } ++ ++ // Get NMS state safely + net.minecraft.world.level.block.state.BlockState iblockdata = this.getNMS(); + net.minecraft.world.level.block.Block block = iblockdata.getBlock(); + net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item); +- boolean result = false; +- +- // Modelled off EntityHuman#hasBlock +- if (block != Blocks.AIR && (item == null || !iblockdata.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(iblockdata))) { +- net.minecraft.world.level.block.Block.dropResources(iblockdata, this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), null, nmsItem, false); // Paper - Properly handle xp dropping +- // Paper start - improve Block#breanNaturally +- if (triggerEffect) { +- if (iblockdata.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock) { +- this.world.levelEvent(net.minecraft.world.level.block.LevelEvent.SOUND_EXTINGUISH_FIRE, this.position, 0); +- } else { +- this.world.levelEvent(net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK, this.position, net.minecraft.world.level.block.Block.getId(iblockdata)); ++ boolean droppedItems = false; ++ ++ // Experience dropping requires ServerLevel ++ ServerLevel serverLevelForDrops = getServerLevel(); // Re-get ServerLevel specifically for drop logic ++ ++ if (serverLevelForDrops != null) { // Only attempt drops/XP if we have a ServerLevel ++ // Check if block should drop items ++ if (!iblockdata.isAir() && (item == null || !iblockdata.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(iblockdata))) { ++ // Drop items using ServerLevel ++ net.minecraft.world.level.block.Block.dropResources(iblockdata, serverLevelForDrops, this.position, this.world.getBlockEntity(this.position), null, nmsItem, false); ++ ++ // Trigger effect using LevelAccessor (safe) ++ if (triggerEffect) { ++ int eventId = (iblockdata.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock) ++ ? net.minecraft.world.level.block.LevelEvent.SOUND_EXTINGUISH_FIRE ++ : net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK; ++ int eventData = (eventId == net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK) ++ ? net.minecraft.world.level.block.Block.getId(iblockdata) : 0; ++ this.world.levelEvent(eventId, this.position, eventData); + } ++ // Drop experience using ServerLevel ++ if (dropExperience) { ++ int xp = block.getExpDrop(iblockdata, serverLevelForDrops, this.position, nmsItem, true); ++ if (xp > 0) { // Only pop if there's XP to drop ++ block.popExperience(serverLevelForDrops, this.position, xp); ++ } ++ } ++ droppedItems = true; ++ } ++ } else { ++ // Log if we couldn't drop XP because it wasn't a ServerLevel ++ if (dropExperience && !iblockdata.isAir()) { // Only warn if XP was requested and block wasn't air ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Cannot drop experience for breakNaturally: Not a ServerLevel."); ++ } ++ // Still trigger effects if requested and possible with LevelAccessor ++ if (triggerEffect && !iblockdata.isAir()) { ++ int eventId = (iblockdata.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock) ++ ? net.minecraft.world.level.block.LevelEvent.SOUND_EXTINGUISH_FIRE ++ : net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK; ++ int eventData = (eventId == net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK) ++ ? net.minecraft.world.level.block.Block.getId(iblockdata) : 0; ++ this.world.levelEvent(eventId, this.position, eventData); + } +- if (dropExperience) block.popExperience(this.world.getMinecraftWorld(), this.position, block.getExpDrop(iblockdata, this.world.getMinecraftWorld(), this.position, nmsItem, true)); +- // Paper end +- result = true; + } + +- // SPIGOT-6778: Directly call setBlock instead of setTypeAndData, so that the tile entiy is not removed and custom remove logic is run. +- // Paper start - improve breakNaturally ++ // Remove the block using LevelAccessor (safe) + boolean destroyed = this.world.removeBlock(this.position, false); + if (destroyed) { ++ // Call destroy hook using LevelAccessor (safe) + block.destroy(this.world, this.position, iblockdata); + } +- if (result) { +- // special cases ++ ++ // Special case handling - requires Level, check again ++ if (droppedItems && this.world instanceof net.minecraft.world.level.Level nmsLevelForSpecialCases) { + if (block instanceof net.minecraft.world.level.block.IceBlock iceBlock) { +- iceBlock.afterDestroy(this.world.getMinecraftWorld(), this.position, nmsItem); ++ iceBlock.afterDestroy(nmsLevelForSpecialCases, this.position, nmsItem); + } else if (block instanceof net.minecraft.world.level.block.TurtleEggBlock turtleEggBlock) { +- turtleEggBlock.decreaseEggs(this.world.getMinecraftWorld(), this.position, iblockdata); ++ turtleEggBlock.decreaseEggs(nmsLevelForSpecialCases, this.position, iblockdata); + } ++ } else if (droppedItems) { // Log if special cases couldn't run ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Cannot perform post-break special actions: Not a Level."); + } +- return destroyed && result; +- // Paper end ++ ++ // Return true if the block was successfully destroyed AND items were dropped (or would have dropped if tool was right) ++ return destroyed && droppedItems; ++ // Leaf end - SparklyPaper - parallel world ticking - Write operation check + } @Override public boolean applyBoneMeal(BlockFace face) { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ // Leaf start - SparklyPaper - parallel world ticking - Write operation check ++ ServerLevel level = getServerLevel(); ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ if (level != null) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, position, "PWT: Cannot modify world asynchronously (applyBoneMeal)"); ++ } else { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Attempted applyBoneMeal on non-ServerLevel - safety not guaranteed."); ++ return false; // Cannot bonemeal without ServerLevel ++ } ++ } else if (level == null) { ++ // If PWT is off, but it's still not a ServerLevel, we also can't bonemeal ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Cannot apply bonemeal: Not a ServerLevel."); ++ return false; + } -+ // SparklyPaper end - parallel world ticking ++ ++ // Original logic requires ServerLevel (level is guaranteed non-null here) Direction direction = CraftBlock.blockFaceToNotch(face); BlockFertilizeEvent event = null; - ServerLevel world = this.getCraftWorld().getHandle(); -@@ -554,8 +605,8 @@ public class CraftBlock implements Block { - world.captureTreeGeneration = false; +- ServerLevel world = this.getCraftWorld().getHandle(); +- UseOnContext context = new UseOnContext(world, null, InteractionHand.MAIN_HAND, Items.BONE_MEAL.getDefaultInstance(), new BlockHitResult(Vec3.ZERO, direction, this.getPosition(), false)); ++ UseOnContext context = new UseOnContext(level, null, InteractionHand.MAIN_HAND, Items.BONE_MEAL.getDefaultInstance(), new BlockHitResult(Vec3.ZERO, direction, this.getPosition(), false)); - if (world.capturedBlockStates.size() > 0) { +- // SPIGOT-6895: Call StructureGrowEvent and BlockFertilizeEvent +- world.captureTreeGeneration = true; ++ level.captureTreeGeneration = true; + InteractionResult result = BoneMealItem.applyBonemeal(context); +- world.captureTreeGeneration = false; +- +- if (world.capturedBlockStates.size() > 0) { - TreeType treeType = SaplingBlock.treeType; - SaplingBlock.treeType = null; -+ TreeType treeType = SaplingBlock.getTreeTypeRT(); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) -+ SaplingBlock.setTreeTypeRT(null); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) - List blocks = new ArrayList<>(world.capturedBlockStates.values()); - world.capturedBlockStates.clear(); +- List blocks = new ArrayList<>(world.capturedBlockStates.values()); +- world.capturedBlockStates.clear(); ++ level.captureTreeGeneration = false; ++ ++ if (level.capturedBlockStates.size() > 0) { ++ TreeType treeType = SaplingBlock.getTreeTypeRT(); // Use thread-local getter ++ SaplingBlock.setTreeTypeRT(null); // Use thread-local setter ++ // Need Bukkit BlockState list for events ++ List bukkitStates = new ArrayList<>(level.capturedBlockStates.values()); ++ level.capturedBlockStates.clear(); // Clear NMS map StructureGrowEvent structureEvent = null; -@@ -644,6 +695,11 @@ public class CraftBlock implements Block { + + if (treeType != null) { +- structureEvent = new StructureGrowEvent(this.getLocation(), treeType, true, null, blocks); ++ structureEvent = new StructureGrowEvent(this.getLocation(), treeType, true, null, bukkitStates); + Bukkit.getPluginManager().callEvent(structureEvent); + } + +- event = new BlockFertilizeEvent(CraftBlock.at(world, this.getPosition()), null, blocks); ++ event = new BlockFertilizeEvent(this, null, bukkitStates); // Use 'this' as the CraftBlock + event.setCancelled(structureEvent != null && structureEvent.isCancelled()); + Bukkit.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { +- for (BlockState blockstate : blocks) { +- blockstate.update(true); +- world.checkCapturedTreeStateForObserverNotify(this.position, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed ++ for (org.bukkit.block.BlockState blockstate : bukkitStates) { ++ blockstate.update(true); // This performs the actual block changes ++ // Notify observers using the captured state info ++ level.checkCapturedTreeStateForObserverNotify(this.position, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); + } + } + } + ++ // Return true only if bonemeal succeeded AND the event wasn't cancelled + return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled()); ++ // Leaf end - SparklyPaper - parallel world ticking - Write operation check + } + + @Override +@@ -592,31 +933,70 @@ public class CraftBlock implements Block { + + @Override + public Collection getDrops(ItemStack item, Entity entity) { +- net.minecraft.world.level.block.state.BlockState iblockdata = this.getNMS(); +- net.minecraft.world.item.ItemStack nms = CraftItemStack.asNMSCopy(item); ++ // Leaf start - SparklyPaper - parallel world ticking - Write operation check ++ // Requires ServerLevel for Block.getDrops call ++ ServerLevel level = getServerLevel(); ++ if (level == null) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Cannot getDrops: Not a ServerLevel."); ++ return Collections.emptyList(); ++ } + +- // Modelled off EntityHuman#hasBlock +- if (item == null || CraftBlockData.isPreferredTool(iblockdata, nms)) { +- return net.minecraft.world.level.block.Block.getDrops(iblockdata, (ServerLevel) this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), entity == null ? null : ((CraftEntity) entity).getHandle(), nms) +- .stream().map(CraftItemStack::asBukkitCopy).collect(Collectors.toList()); +- } else { ++ // Strict check if applicable ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "getDrops"); ++ ++ try { ++ net.minecraft.world.level.block.state.BlockState iblockdata = this.getNMS(); // Use safe getNMS ++ net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item); ++ net.minecraft.world.entity.Entity nmsEntity = (entity == null) ? null : ((CraftEntity) entity).getHandle(); ++ ++ // Check tool requirement ++ if (item == null || !iblockdata.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(iblockdata)) { ++ // Call NMS getDrops using the verified ServerLevel ++ List nmsDrops = net.minecraft.world.level.block.Block.getDrops( ++ iblockdata, level, this.position, this.world.getBlockEntity(this.position), nmsEntity, nmsItem ++ ); ++ // Convert to Bukkit ItemStacks ++ return nmsDrops.stream().map(CraftItemStack::asBukkitCopy).collect(Collectors.toList()); ++ } else { ++ // Tool was required but not correct/present ++ return Collections.emptyList(); ++ } ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getDrops", e); + return Collections.emptyList(); + } ++ // Leaf end - SparklyPaper - parallel world ticking - Write operation check + } + + @Override + public boolean isPreferredTool(ItemStack item) { ++ // Leaf - SparklyPaper - parallel world ticking - Uses safe getNMS() + net.minecraft.world.level.block.state.BlockState iblockdata = this.getNMS(); +- net.minecraft.world.item.ItemStack nms = CraftItemStack.asNMSCopy(item); +- return CraftBlockData.isPreferredTool(iblockdata, nms); ++ net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item); ++ return iblockdata.requiresCorrectToolForDrops() && nmsItem.isCorrectToolForDrops(iblockdata); // Leaf - SparklyPaper - parallel world ticking - Delegate to helper which checks tool tags + } + + @Override + public float getBreakSpeed(Player player) { + Preconditions.checkArgument(player != null, "player cannot be null"); +- return this.getNMS().getDestroyProgress(((CraftPlayer) player).getHandle(), this.world, this.position); ++ // Leaf start - SparklyPaper - parallel world ticking ++ // Requires LevelReader (LevelAccessor is sufficient) ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "getBreakSpeed"); // Strict check if applicable ++ ++ try { ++ // Uses safe getNMS() ++ return this.getNMS().getDestroyProgress(((CraftPlayer) player).getHandle(), this.world, this.position); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getBreakSpeed" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return 0.0f; ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + ++ // Leaf - SparklyPaper - parallel world ticking - Metadata methods delegate to CraftWorld, assumed safe + @Override + public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { + this.getCraftWorld().getBlockMetadata().setMetadata(this, metadataKey, newMetadataValue); +@@ -639,57 +1019,147 @@ public class CraftBlock implements Block { + + @Override + public boolean isPassable() { +- return this.getNMS().getCollisionShape(this.world, this.position).isEmpty(); ++ // Leaf start - SparklyPaper - parallel world ticking ++ // Requires LevelReader (LevelAccessor is sufficient) ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "isPassable"); // Strict check if applicable ++ ++ try { ++ // Uses safe getNMS() ++ VoxelShape shape = this.getNMS().getCollisionShape(this.world, this.position); ++ return shape.isEmpty(); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for isPassable" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return true; // Default to passable on error? Or false? Passable seems safer. ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } @Override public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); -+ } -+ // SparklyPaper end - parallel world ticking ++ // Leaf start - SparklyPaper - parallel world ticking ++ // Validate inputs Preconditions.checkArgument(start != null, "Location start cannot be null"); - Preconditions.checkArgument(this.getWorld().equals(start.getWorld()), "Location start cannot be a different world"); +- Preconditions.checkArgument(this.getWorld().equals(start.getWorld()), "Location start cannot be a different world"); ++ Preconditions.checkArgument(this.getWorld().equals(start.getWorld()), "Location start cannot be in a different world"); start.checkFinite(); -@@ -685,6 +741,11 @@ public class CraftBlock implements Block { + + Preconditions.checkArgument(direction != null, "Vector direction cannot be null"); + direction.checkFinite(); +- Preconditions.checkArgument(direction.lengthSquared() > 0, "Direction's magnitude (%s) must be greater than 0", direction.lengthSquared()); ++ Preconditions.checkArgument(direction.lengthSquared() > 1.0E-8, "Direction's magnitude (%s) must be greater than 0", direction.lengthSquared()); // Avoid near-zero vectors + + Preconditions.checkArgument(fluidCollisionMode != null, "FluidCollisionMode cannot be null"); +- if (maxDistance < 0.0D) { +- return null; +- } +- +- Vector dir = direction.clone().normalize().multiply(maxDistance); +- Vec3 startPos = CraftLocation.toVec3D(start); +- Vec3 endPos = startPos.add(dir.getX(), dir.getY(), dir.getZ()); ++ if (maxDistance < 0.0D) return null; // Max distance must be non-negative ++ ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ ++ if (needsBuffering(level, handlingMode)) { ++ // Buffered path ++ HitResult nmsHitResult = executeBufferedRead( ++ level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_RAY_TRACE, ++ new Object[]{this.position, start, direction, maxDistance, fluidCollisionMode}, // Pass all params ++ null, "rayTrace" ++ ); ++ // Convert NMS result (can be null) to Bukkit result ++ return CraftRayTraceResult.fromNMS(this.getWorld(), nmsHitResult); ++ } else { ++ // Strict/Disabled/Non-ServerLevel path ++ checkStrictMode(level, handlingMode, "rayTrace"); ++ try { ++ // Calculate start and end points for the ray trace ++ Vec3 startPos = CraftLocation.toVec3D(start); ++ Vector dirNormalized = direction.clone().normalize(); // Normalize once ++ Vec3 endPos = startPos.add(dirNormalized.getX() * maxDistance, dirNormalized.getY() * maxDistance, dirNormalized.getZ() * maxDistance); ++ ++ // Perform the clip using LevelAccessor (safe) ++ // Pass the block's position as the context position for the clip function ++ HitResult nmsHitResult = this.world.clip( ++ new ClipContext(startPos, endPos, ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toNMS(fluidCollisionMode), CollisionContext.empty()), ++ this.position // Provide the block's position here ++ ); + +- HitResult nmsHitResult = this.world.clip(new ClipContext(startPos, endPos, ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toNMS(fluidCollisionMode), CollisionContext.empty()), this.position); +- return CraftRayTraceResult.fromNMS(this.getWorld(), nmsHitResult); ++ // Convert NMS result to Bukkit result ++ return CraftRayTraceResult.fromNMS(this.getWorld(), nmsHitResult); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for rayTrace" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return null; // Return null on error ++ } ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override + public BoundingBox getBoundingBox() { +- VoxelShape shape = this.getNMS().getShape(this.world, this.position); +- +- if (shape.isEmpty()) { +- return new BoundingBox(); // Return an empty bounding box if the block has no dimension ++ // Leaf start - SparklyPaper - parallel world ticking ++ // Requires LevelReader (LevelAccessor is sufficient) ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "getBoundingBox"); // Strict check if applicable ++ ++ try { ++ // Uses safe getNMS() ++ VoxelShape shape = this.getNMS().getShape(this.world, this.position); ++ if (shape.isEmpty()) { ++ // Return a zero-sized box at the block's corner if shape is empty ++ return new BoundingBox(getX(), getY(), getZ(), getX(), getY(), getZ()); ++ } ++ // Get AABB relative to 0,0,0 and offset by block position ++ AABB aabb = shape.bounds(); ++ return new BoundingBox( ++ this.getX() + aabb.minX, this.getY() + aabb.minY, this.getZ() + aabb.minZ, ++ this.getX() + aabb.maxX, this.getY() + aabb.maxY, this.getZ() + aabb.maxZ ++ ); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getBoundingBox" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ // Default to a full 1x1x1 box on error ++ return new BoundingBox(getX(), getY(), getZ(), getX() + 1, getY() + 1, getZ() + 1); + } +- +- AABB aabb = shape.bounds(); +- return new BoundingBox(this.getX() + aabb.minX, this.getY() + aabb.minY, this.getZ() + aabb.minZ, this.getX() + aabb.maxX, this.getY() + aabb.maxY, this.getZ() + aabb.maxZ); ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override + public org.bukkit.util.VoxelShape getCollisionShape() { +- VoxelShape shape = this.getNMS().getCollisionShape(this.world, this.position); +- return new CraftVoxelShape(shape); ++ // Leaf start - SparklyPaper - parallel world ticking ++ // Requires LevelReader (LevelAccessor is sufficient) ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "getCollisionShape"); // Strict check if applicable ++ ++ try { ++ // Uses safe getNMS() ++ VoxelShape shape = this.getNMS().getCollisionShape(this.world, this.position); ++ return new CraftVoxelShape(shape); // Wrap NMS shape ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getCollisionShape" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return new CraftVoxelShape(net.minecraft.world.phys.shapes.Shapes.empty()); // Default to empty shape on error ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } @Override public boolean canPlace(BlockData data) { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); -+ } -+ // SparklyPaper end - parallel world ticking Preconditions.checkArgument(data != null, "BlockData cannot be null"); - net.minecraft.world.level.block.state.BlockState iblockdata = ((CraftBlockData) data).getState(); - net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); -@@ -719,6 +780,11 @@ public class CraftBlock implements Block { +- net.minecraft.world.level.block.state.BlockState iblockdata = ((CraftBlockData) data).getState(); +- net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; + +- return iblockdata.canSurvive(world, this.position); ++ if (needsBuffering(level, handlingMode)) { ++ // Buffered path ++ return executeBufferedRead(level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_CAN_PLACE, new Object[]{this.position, data}, false, "canPlace"); ++ } else { ++ // Strict/Disabled/Non-ServerLevel path ++ checkStrictMode(level, handlingMode, "canPlace"); ++ try { ++ net.minecraft.world.level.block.state.BlockState nmsData = ((CraftBlockData) data).getState(); ++ // LevelAccessor has canSurvive ++ return nmsData.canSurvive(this.world, this.position); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for canPlace" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return false; // Default to false on error ++ } ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override +@@ -700,7 +1170,10 @@ public class CraftBlock implements Block { + // Paper start + @Override + public com.destroystokyo.paper.block.BlockSoundGroup getSoundGroup() { +- return new com.destroystokyo.paper.block.CraftBlockSoundGroup(getNMS().getBlock().defaultBlockState().getSoundType()); ++ // Leaf start - SparklyPaper - parallel world ticking ++ net.minecraft.world.level.block.SoundType nmsSoundType = this.getNMS().getSoundType(); ++ return new com.destroystokyo.paper.block.CraftBlockSoundGroup(nmsSoundType); ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override +@@ -713,26 +1186,76 @@ public class CraftBlock implements Block { + return this.getNMS().getBlock().getDescriptionId(); + } + ++ // Leaf start - SparklyPaper - parallel world ticking ++ @Override + public boolean isValidTool(ItemStack itemStack) { +- return getDrops(itemStack).size() != 0; ++ if (itemStack == null || itemStack.getType().isAir()) { ++ return false; ++ } ++ return !this.getDrops(itemStack).isEmpty(); ++ // Leaf end - SparklyPaper - parallel world ticking + } @Override public void tick() { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); +- final ServerLevel level = this.world.getMinecraftWorld(); ++ // Leaf start - SparklyPaper - parallel world ticking - Write operation check ++ // (block ticks can modify state) ++ ServerLevel level = getServerLevel(); ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ if (level != null) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, position, "PWT: Cannot tick block asynchronously (tick)"); ++ } else { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Attempted tick on non-ServerLevel - safety not guaranteed."); ++ return; // Cannot tick without ServerLevel ++ } ++ } else if (level == null) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Cannot tick block: Not a ServerLevel."); ++ return; + } -+ // SparklyPaper end - parallel world ticking - final ServerLevel level = this.world.getMinecraftWorld(); ++ this.getNMS().tick(level, this.position, level.random); ++ // Leaf end - SparklyPaper - parallel world ticking - } + + + @Override + public void fluidTick() { +- this.getNMSFluid().tick(this.world.getMinecraftWorld(), this.position, this.getNMS()); ++ // Leaf start - SparklyPaper - parallel world ticking ++ // Fluid ticks can modify state ++ ServerLevel level = getServerLevel(); ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ if (level != null) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, position, "PWT: Cannot tick fluid asynchronously (fluidTick)"); ++ } else { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Attempted fluidTick on non-ServerLevel - safety not guaranteed."); ++ return; // Cannot tick fluid without ServerLevel ++ } ++ } else if (level == null) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Cannot tick fluid: Not a ServerLevel."); ++ return; ++ } ++ this.getNMSFluid().tick(level, this.position, this.getNMS()); ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override + public void randomTick() { +- final ServerLevel level = this.world.getMinecraftWorld(); ++ // Leaf start - SparklyPaper - parallel world ticking - Write operation check ++ // (random ticks can modify state) ++ ServerLevel level = getServerLevel(); ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ if (level != null) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, position, "PWT: Cannot randomTick block asynchronously"); ++ } else { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Attempted randomTick on non-ServerLevel - safety not guaranteed."); ++ return; ++ } ++ } else if (level == null) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Cannot randomTick block: Not a ServerLevel."); ++ return; ++ } + this.getNMS().randomTick(level, this.position, level.random); ++ // Leaf end - SparklyPaper - parallel world ticking - Write operation check + } + // Paper end + } diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java index 768d3f93da2522d467183654260a8bd8653588b1..5cef786fa2e5dfd3e7b79918bc73af02891b0bea 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/world/ReadOperationType.java b/leaf-server/src/main/java/org/dreeam/leaf/async/world/ReadOperationType.java new file mode 100644 index 00000000..dffe7ba0 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/world/ReadOperationType.java @@ -0,0 +1,11 @@ +package org.dreeam.leaf.async.world; + +public enum ReadOperationType { + BLOCK_GET_BIOME, + BLOCK_GET_COMPUTED_BIOME, + BLOCK_IS_INDIRECTLY_POWERED, + BLOCK_GET_BLOCK_POWER, + BLOCK_RAY_TRACE, + BLOCK_CAN_PLACE, + BLOCK_GET_NMS_STATE +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/world/WorldReadRequest.java b/leaf-server/src/main/java/org/dreeam/leaf/async/world/WorldReadRequest.java new file mode 100644 index 00000000..8fb45b7c --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/world/WorldReadRequest.java @@ -0,0 +1,10 @@ +package org.dreeam.leaf.async.world; + +import java.util.concurrent.CompletableFuture; + +public record WorldReadRequest( + ReadOperationType type, + Object[] params, // Parameters for the read operation + CompletableFuture future // Future to complete with the result +) { +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java index 1c1c4efc..f4b516f3 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java @@ -8,15 +8,19 @@ import org.dreeam.leaf.config.annotations.Experimental; public class SparklyPaperParallelWorldTicking extends ConfigModules { public String getBasePath() { - return EnumConfigCategory.ASYNC.getBaseKeyName() + ".parallel-world-tracking"; - } // TODO: Correct config key when stable + // Corrected path based on your comment + return EnumConfigCategory.ASYNC.getBaseKeyName() + ".parallel-world-ticking"; + } @Experimental public static boolean enabled = false; public static int threads = 8; public static boolean logContainerCreationStacktraces = false; public static boolean disableHardThrow = false; + @Deprecated public static boolean runAsyncTasksSync = false; + // STRICT, BUFFERED, DISABLED + public static String asyncUnsafeReadHandling = "BUFFERED"; @Override public void onLoaded() { @@ -34,15 +38,30 @@ public class SparklyPaperParallelWorldTicking extends ConfigModules { } else { threads = 0; } + logContainerCreationStacktraces = config.getBoolean(getBasePath() + ".log-container-creation-stacktraces", logContainerCreationStacktraces); logContainerCreationStacktraces = enabled && logContainerCreationStacktraces; disableHardThrow = config.getBoolean(getBasePath() + ".disable-hard-throw", disableHardThrow); disableHardThrow = enabled && disableHardThrow; - runAsyncTasksSync = config.getBoolean(getBasePath() + ".run-async-tasks-sync", runAsyncTasksSync); - runAsyncTasksSync = enabled && runAsyncTasksSync; + asyncUnsafeReadHandling = config.getString(getBasePath() + ".async-unsafe-read-handling", asyncUnsafeReadHandling).toUpperCase(); + + if (!asyncUnsafeReadHandling.equals("STRICT") && !asyncUnsafeReadHandling.equals("BUFFERED") && !asyncUnsafeReadHandling.equals("DISABLED")) { + System.err.println("[Leaf] Invalid value for " + getBasePath() + ".async-unsafe-read-handling: " + asyncUnsafeReadHandling + ". Defaulting to STRICT."); + asyncUnsafeReadHandling = "STRICT"; + } + if (!enabled) { + asyncUnsafeReadHandling = "DISABLED"; + } + + runAsyncTasksSync = config.getBoolean(getBasePath() + ".run-async-tasks-sync", false); // Default to false now + if (runAsyncTasksSync) { + System.err.println("[Leaf] WARNING: The setting '" + getBasePath() + ".run-async-tasks-sync' is deprecated. Use 'async-unsafe-read-handling: STRICT' for similar safety checks or 'BUFFERED' for buffered reads."); + } if (enabled) { LeafConfig.LOGGER.info("Using {} threads for Parallel World Ticking", threads); } + + runAsyncTasksSync = enabled && runAsyncTasksSync; // Auto-disable if main feature is off } } From 12711630d4ca8788366f8abd47275c4c262ff13f Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Tue, 3 Jun 2025 01:46:26 +0800 Subject: [PATCH 25/25] Use Leaf config logger --- .../modules/async/SparklyPaperParallelWorldTicking.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java index f4b516f3..304e234f 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java @@ -8,7 +8,6 @@ import org.dreeam.leaf.config.annotations.Experimental; public class SparklyPaperParallelWorldTicking extends ConfigModules { public String getBasePath() { - // Corrected path based on your comment return EnumConfigCategory.ASYNC.getBaseKeyName() + ".parallel-world-ticking"; } @@ -46,7 +45,7 @@ public class SparklyPaperParallelWorldTicking extends ConfigModules { asyncUnsafeReadHandling = config.getString(getBasePath() + ".async-unsafe-read-handling", asyncUnsafeReadHandling).toUpperCase(); if (!asyncUnsafeReadHandling.equals("STRICT") && !asyncUnsafeReadHandling.equals("BUFFERED") && !asyncUnsafeReadHandling.equals("DISABLED")) { - System.err.println("[Leaf] Invalid value for " + getBasePath() + ".async-unsafe-read-handling: " + asyncUnsafeReadHandling + ". Defaulting to STRICT."); + LeafConfig.LOGGER.warn("Invalid value for {}.async-unsafe-read-handling: {}, fallback to STRICT.", getBasePath(), asyncUnsafeReadHandling); asyncUnsafeReadHandling = "STRICT"; } if (!enabled) { @@ -55,7 +54,7 @@ public class SparklyPaperParallelWorldTicking extends ConfigModules { runAsyncTasksSync = config.getBoolean(getBasePath() + ".run-async-tasks-sync", false); // Default to false now if (runAsyncTasksSync) { - System.err.println("[Leaf] WARNING: The setting '" + getBasePath() + ".run-async-tasks-sync' is deprecated. Use 'async-unsafe-read-handling: STRICT' for similar safety checks or 'BUFFERED' for buffered reads."); + LeafConfig.LOGGER.warn("The setting '{}.run-async-tasks-sync' is deprecated. Use 'async-unsafe-read-handling: STRICT' for similar safety checks or 'BUFFERED' for buffered reads.", getBasePath()); } if (enabled) {