From 35ac5d48ed4846a669e2a89fcd23288cad2bf0c8 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Sat, 25 Oct 2025 07:41:43 -0400 Subject: [PATCH] Cleanup PWT (#533) * Unify comment format * More configurable * Remove one extra execute mid-tick task call in level tick when PWT is disabled This may cause extremely rare, weird, strange, magic, mysterious issues with plugins, or potentially more. One example is that it may cause boss mob duplication issue when `ONE MOB ONLY` was enabled in plugin SupremeBosses --- ...-SparklyPaper-Parallel-world-ticking.patch | 940 ++++----- ...1-SparklyPaper-Track-each-world-MSPT.patch | 10 +- .../0205-Use-BFS-on-getSlopeDistance.patch | 4 +- ...-BlockEntity-ticking-isRemoved-check.patch | 6 +- ...-Micro-optimizations-for-random-tick.patch | 6 +- ...hunk-retrieving-in-entity-fluid-push.patch | 6 +- ...ket-on-player-join-to-avoid-chunk-lo.patch | 4 +- .../features/0225-Protocol-Core.patch | 10 +- .../features/0237-Optimize-isEyeInFluid.patch | 6 +- .../features/0243-optimize-mob-spawning.patch | 6 +- .../0248-Use-UUID-for-cure-reputation.patch | 4 +- .../features/0250-optimize-random-tick.patch | 10 +- .../features/0255-optimize-waypoint.patch | 6 +- .../features/0256-Paw-optimization.patch | 16 +- ...fig-fixClimbingBypassingCrammingRule.patch | 4 +- .../features/0268-Bump-netty-to-4.2.x.patch | 6 +- ...Paper-PR-Optimise-temptation-lookups.patch | 6 +- .../0270-fix-temptation-lookups.patch | 4 +- ...71-Lithium-combined-heightmap-update.patch | 4 +- .../0274-thread-unsafe-chunk-map.patch | 16 +- ...0277-remove-shouldTickBlocksAt-check.patch | 4 +- .../features/0283-optimize-onClimbable.patch | 4 +- .../0287-cache-eye-block-position.patch | 6 +- .../0288-optimize-entity-in-fluid.patch | 8 +- .../features/0292-cache-collision-list.patch | 6 +- .../features/0293-fast-bit-radix-sort.patch | 4 +- ...Pluto-Expose-Direction-Plane-s-faces.patch | 18 +- ...-SparklyPaper-Parallel-world-ticking.patch | 1836 +++++++++-------- ...0047-Fish-Parallel-World-Ticking-API.patch | 4 +- .../features/0057-optimize-mob-spawning.patch | 6 +- 30 files changed, 1551 insertions(+), 1419 deletions(-) diff --git a/leaf-server/minecraft-patches/features/0200-SparklyPaper-Parallel-world-ticking.patch b/leaf-server/minecraft-patches/features/0200-SparklyPaper-Parallel-world-ticking.patch index 11e1a46e..99e095d5 100644 --- a/leaf-server/minecraft-patches/features/0200-SparklyPaper-Parallel-world-ticking.patch +++ b/leaf-server/minecraft-patches/features/0200-SparklyPaper-Parallel-world-ticking.patch @@ -1,14 +1,18 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Altiami -Date: Wed, 5 Mar 2025 13:13:24 -0800 +From: MrPowerGamerBR +Date: Sun, 25 May 2025 21:39:32 -0300 Subject: [PATCH] SparklyPaper: Parallel world ticking Original project: https://github.com/SparklyPower/SparklyPaper -Commit: 589225495e566c60feb907a3571c1ccba855b6ed +Co-authored-by: Altiami +Co-authored-by: Taiyou06 +Co-authored-by: MrlingXD <90316914+wling-art@users.noreply.github.com> + +Commit: e2e154fb764650c90bbd23c9cede2b11ba1a08e2 diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java -index 0f5966932c4211922eccac09ab164fcb69dad582..36ce2be30478d734d8326eeeb004ba7dc61e0642 100644 +index 0f5966932c4211922eccac09ab164fcb69dad582..f1e1e96a425ae0186164b3ae6caca226fdd7e12e 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java @@ -1142,7 +1142,7 @@ public final class ChunkHolderManager { @@ -16,35 +20,36 @@ index 0f5966932c4211922eccac09ab164fcb69dad582..36ce2be30478d734d8326eeeb004ba7d return; } - if (!TickThread.isTickThread()) { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && !TickThread.isTickThreadFor(world) || !TickThread.isTickThread()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (make configurable) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && !TickThread.isTickThreadFor(world) || !TickThread.isTickThread()) { // Leaf - SparklyPaper - parallel world ticking // These will be handled on the next ServerChunkCache$MainThreadExecutor#pollTask, as it runs the distance manager update // which will invoke processTicketUpdates this.offThreadPendingFullLoadUpdate.addAll(changedFullStatus); -@@ -1163,7 +1163,12 @@ public final class ChunkHolderManager { +@@ -1163,7 +1163,13 @@ public final class ChunkHolderManager { // note: never call while inside the chunk system, this will absolutely break everything public void processUnloads() { - TickThread.ensureTickThread("Cannot unload chunks off-main"); -+ // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) -+ TickThread.ensureTickThread(world, "Cannot unload chunks off-main"); // SparklyPaper - parallel world ticking -+ else ++ // Leaf start - SparklyPaper - parallel world ticking ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ TickThread.ensureTickThread(world, "Cannot unload chunks off-main"); ++ } else { + TickThread.ensureTickThread("Cannot unload chunks off-main"); -+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) ++ } ++ // Leaf end - SparklyPaper - parallel world ticking if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { throw new IllegalStateException("Cannot unload chunks recursively"); -@@ -1429,7 +1434,7 @@ public final class ChunkHolderManager { +@@ -1429,7 +1435,7 @@ public final class ChunkHolderManager { if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { throw new IllegalStateException("Cannot update ticket level while unloading chunks or updating entity manager"); } - final boolean isTickThread = TickThread.isTickThread(); -+ final boolean isTickThread = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && TickThread.isTickThreadFor(world) || TickThread.isTickThread(); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (make configurable) ++ final boolean isTickThread = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? TickThread.isTickThreadFor(world) : TickThread.isTickThread(); // Leaf - SparklyPaper - parallel world ticking if (!PlatformHooks.get().allowAsyncTicketUpdates() && isTickThread) { TickThread.ensureTickThread("Cannot asynchronously process ticket updates"); diff --git a/io/papermc/paper/entity/activation/ActivationRange.java b/io/papermc/paper/entity/activation/ActivationRange.java -index ea01daf583ddd110f153304a6a65ac2d765b9d31..f127231587fef0cef6767a259e0e1294650e1330 100644 +index ea01daf583ddd110f153304a6a65ac2d765b9d31..b106bbc9334ac345390592d6e152edef1d317ed2 100644 --- a/io/papermc/paper/entity/activation/ActivationRange.java +++ b/io/papermc/paper/entity/activation/ActivationRange.java @@ -134,7 +134,7 @@ public final class ActivationRange { @@ -52,15 +57,15 @@ index ea01daf583ddd110f153304a6a65ac2d765b9d31..f127231587fef0cef6767a259e0e1294 * @param world */ - public static void activateEntities(final Level world) { -+ public synchronized static void activateEntities(final Level world) { // Leaf ++ public synchronized static void activateEntities(final Level world) { // Leaf - SparklyPaper - parallel world ticking final int miscActivationRange = world.spigotConfig.miscActivationRange; final int raiderActivationRange = world.spigotConfig.raiderActivationRange; final int animalActivationRange = world.spigotConfig.animalActivationRange; diff --git a/io/papermc/paper/redstone/RedstoneWireTurbo.java b/io/papermc/paper/redstone/RedstoneWireTurbo.java -index ff747a1ecdf3c888bca0d69de4f85dcd810b6139..b288d57d9f7bd0ccf1877cf9920bd67288ff22f7 100644 +index ff747a1ecdf3c888bca0d69de4f85dcd810b6139..0838b3d60e94a280a6fdd1aef413078a0ec71a1c 100644 --- a/io/papermc/paper/redstone/RedstoneWireTurbo.java +++ b/io/papermc/paper/redstone/RedstoneWireTurbo.java -@@ -829,14 +829,10 @@ public final class RedstoneWireTurbo { +@@ -829,14 +829,22 @@ public final class RedstoneWireTurbo { j = getMaxCurrentStrength(upd, j); int l = 0; @@ -73,14 +78,26 @@ index ff747a1ecdf3c888bca0d69de4f85dcd810b6139..b288d57d9f7bd0ccf1877cf9920bd672 - final int k = worldIn.getBestNeighborSignal(upd.self); - wire.shouldSignal = true; + // Leaf start - SparklyPaper - parallel world ticking -+ // This now correctly calls the (conditionally) thread-safe method in RedStoneWireBlock -+ final int k = wire.getBlockSignal(worldIn, upd.self); ++ final int k; ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ // This now correctly calls the (conditionally) thread-safe method in RedStoneWireBlock ++ k = wire.getBlockSignal(worldIn, upd.self); ++ } else { ++ wire.shouldSignal = false; ++ // Unfortunately, World.isBlockIndirectlyGettingPowered is complicated, ++ // and I'm not ready to try to replicate even more functionality from ++ // elsewhere in Minecraft into this accelerator. So sadly, we must ++ // suffer the performance hit of this very expensive call. If there ++ // is consistency to what this call returns, we may be able to cache it. ++ k = worldIn.getBestNeighborSignal(upd.self); ++ wire.shouldSignal = true; ++ } + // Leaf end - SparklyPaper - parallel world ticking // The variable 'k' holds the maximum redstone power value of any adjacent blocks. // If 'k' has the highest level of all neighbors, then the power level of this diff --git a/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java -index 582e012222123e5001c34153f2ee1ab1d08935fd..c0bce2293d07ca58cc5bc9e036ab8dcac06c1566 100644 +index 582e012222123e5001c34153f2ee1ab1d08935fd..e90201f4d878874e380d12d82220fc6cd1df9b9c 100644 --- a/net/minecraft/core/dispenser/DispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java @@ -401,8 +401,8 @@ public interface DispenseItemBehavior { @@ -89,29 +106,29 @@ index 582e012222123e5001c34153f2ee1ab1d08935fd..c0bce2293d07ca58cc5bc9e036ab8dca if (!level.capturedBlockStates.isEmpty()) { - org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; - net.minecraft.world.level.block.SaplingBlock.treeType = null; -+ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.getTreeTypeRT(); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) -+ net.minecraft.world.level.block.SaplingBlock.setTreeTypeRT(null); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) ++ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.getTreeTypeRT(); // Leaf - SparklyPaper - parallel world ticking ++ net.minecraft.world.level.block.SaplingBlock.setTreeTypeRT(null); // Leaf - SparklyPaper - parallel world ticking org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(blockPos, level.getWorld()); List states = new java.util.ArrayList<>(level.capturedBlockStates.values()); level.capturedBlockStates.clear(); diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index f8dd55a4a5a93788e0690b1a6a6b87d12cf26571..b81de5b278c43a404d0f6b96cf134b401f137ab3 100644 +index f8dd55a4a5a93788e0690b1a6a6b87d12cf26571..51efe9aadb377c35d6ed2b7eadead3c9e1c745f8 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -290,6 +290,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function threadFunction) { ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system -@@ -322,24 +323,36 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop serverPlayer1.connection.suspendFlushing()); this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit -@@ -1710,28 +1741,50 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> tasks = new java.util.ArrayDeque<>(); + try { + for (ServerLevel serverLevel : this.getAllLevels()) { @@ -224,24 +240,23 @@ index f8dd55a4a5a93788e0690b1a6a6b87d12cf26571..b81de5b278c43a404d0f6b96cf134b40 - CrashReport crashReport = CrashReport.forThrowable(var7, "Exception ticking world"); - serverLevel.fillReportDetails(crashReport); - throw new ReportedException(crashReport); -+ // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) + if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { + serverLevelTickingSemaphore.acquire(); + tasks.add( + serverLevel.tickExecutor.submit(() -> { + ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread currentThread = (ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread) Thread.currentThread(); -+ currentThread.currentlyTickingServerLevel = serverLevel; ++ currentThread.currentTickingServerLevel = serverLevel; + + try { -+ tickLevel(serverLevel, hasTimeLeft); // Leaf - SparklyPaper - parallel world ticking mod (move level ticking logic out for branch convergence) ++ tickLevel(serverLevel, hasTimeLeft); // Leaf - SparklyPaper - parallel world ticking - move level ticking logic out for branch convergence + } finally { + serverLevelTickingSemaphore.release(); + } + }, serverLevel) + ); -+ } else ++ } else { + tickLevel(serverLevel, hasTimeLeft); -+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) ++ } + + serverLevel.explosionDensityCache.clear(); // Paper - Optimize explosions } @@ -253,46 +268,36 @@ index f8dd55a4a5a93788e0690b1a6a6b87d12cf26571..b81de5b278c43a404d0f6b96cf134b40 + } catch (java.lang.InterruptedException | java.util.concurrent.ExecutionException e) { + throw new RuntimeException(e); // Propagate exception } -+ // SparklyPaper end - parallel world ticking ++ // Leaf end - SparklyPaper - parallel world ticking this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { org.dreeam.leaf.async.tracker.AsyncTracker.onTickEnd(this); } // Leaf - Multithreaded tracker -@@ -1817,6 +1870,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop progress = new LinkedHashMap<>(); private final Set visible = new HashSet<>(); - private final Set progressChanged = new HashSet<>(); - private final Set rootsToUpdate = new HashSet<>(); + // Leaf start - SparklyPaper - parallel world ticking -+ private final Set progressChanged = new HashSet<>(); // Used when PWT is disabled -+ private final Set progressChangedConcurrent = java.util.concurrent.ConcurrentHashMap.newKeySet(); // Used when PWT is enabled ++ private final Set progressChanged = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? java.util.concurrent.ConcurrentHashMap.newKeySet() : new HashSet<>(); + private final Set rootsToUpdate = new HashSet<>(); // Always managed on player tick thread + // Leaf end - SparklyPaper - parallel world ticking private ServerPlayer player; @Nullable private AdvancementHolder lastSelectedTab; -@@ -88,6 +91,7 @@ public class PlayerAdvancements { - this.visible.clear(); - this.rootsToUpdate.clear(); - this.progressChanged.clear(); -+ this.progressChangedConcurrent.clear(); // Leaf - SparklyPaper - parallel world ticking fix - Also clear concurrent set on reload - this.isFirstPacket = true; - this.lastSelectedTab = null; - this.tree = manager.tree(); -@@ -150,7 +154,7 @@ public class PlayerAdvancements { +@@ -150,7 +152,7 @@ public class PlayerAdvancements { if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.ignoredAdvancements) LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", path, this.playerSavePath); // Gale - Purpur - do not log ignored advancements } else { this.startProgress(advancementHolder, progress); @@ -301,33 +306,7 @@ index 4bf87ebb49880b8e09203a48fce6371398281561..27bbe0c43dd9b7f8bf932a6b4825ce2c this.markForVisibilityUpdate(advancementHolder); } }); -@@ -182,10 +186,12 @@ public class PlayerAdvancements { - return false; - } - // Paper end - Add PlayerAdvancementCriterionGrantEvent -- this.unregisterListeners(advancement); -- this.progressChanged.add(advancement); -- flag = true; -- if (!isDone && orStartProgress.isDone()) { -+ // Leaf start - SparklyPaper - parallel world ticking -+ this.unregisterListeners(advancement); // Must unregister criteria listeners -+ (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? this.progressChangedConcurrent : this.progressChanged).add(advancement); -+ flag = true; // Mark progress changed -+ if (!isDone && orStartProgress.isDone()) { // If the advancement was just completed -+ // Leaf end - SparklyPaper - parallel world ticking - // Paper start - Add Adventure message to PlayerAdvancementDoneEvent - final net.kyori.adventure.text.Component message = advancement.value().display().flatMap(info -> { - return java.util.Optional.ofNullable( -@@ -219,12 +225,14 @@ public class PlayerAdvancements { - AdvancementProgress orStartProgress = this.getOrStartProgress(advancement); - boolean isDone = orStartProgress.isDone(); - if (orStartProgress.revokeProgress(criterionKey)) { -- this.registerListeners(advancement); -- this.progressChanged.add(advancement); -+ // Leaf start - SparklyPaper - parallel world ticking -+ this.registerListeners(advancement); // Re-register listeners if it's no longer done -+ (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? this.progressChangedConcurrent : this.progressChanged).add(advancement); -+ // Leaf end - SparklyPaper - parallel world ticking +@@ -224,7 +226,7 @@ public class PlayerAdvancements { flag = true; } @@ -336,94 +315,71 @@ index 4bf87ebb49880b8e09203a48fce6371398281561..27bbe0c43dd9b7f8bf932a6b4825ce2c this.markForVisibilityUpdate(advancement); } -@@ -270,7 +278,10 @@ public class PlayerAdvancements { +@@ -270,6 +272,7 @@ public class PlayerAdvancements { } public void flushDirty(ServerPlayer player, boolean showAdvancements) { -- if (this.isFirstPacket || !this.rootsToUpdate.isEmpty() || !this.progressChanged.isEmpty()) { -+ // Leaf start - SparklyPaper - parallel world ticking -+ final boolean useConcurrent = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled; -+ final Set relevantProgressSet = useConcurrent ? this.progressChangedConcurrent : this.progressChanged; -+ if (this.isFirstPacket || !this.rootsToUpdate.isEmpty() || !relevantProgressSet.isEmpty()) { ++ final boolean isConcurrent = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled; // Leaf - SparklyPaper - parallel world ticking + if (this.isFirstPacket || !this.rootsToUpdate.isEmpty() || !this.progressChanged.isEmpty()) { Map map = new HashMap<>(); Set set = new java.util.TreeSet<>(java.util.Comparator.comparing(adv -> adv.id().toString())); // Paper - Changed from HashSet to TreeSet ordered alphabetically. - Set set1 = new HashSet<>(); -@@ -278,16 +289,24 @@ public class PlayerAdvancements { - for (AdvancementNode advancementNode : this.rootsToUpdate) { - this.updateTreeVisibility(advancementNode, set, set1); - } -+ this.rootsToUpdate.clear(); // Roots processed, clear the set +@@ -281,13 +284,23 @@ public class PlayerAdvancements { -- this.rootsToUpdate.clear(); -+ if (!relevantProgressSet.isEmpty()) { -+ Set toProcess = useConcurrent ? new HashSet<>(relevantProgressSet) : relevantProgressSet; + this.rootsToUpdate.clear(); - for (AdvancementHolder advancementHolder : this.progressChanged) { -- if (this.visible.contains(advancementHolder)) { ++ // Leaf start - SparklyPaper - parallel world ticking ++ if (!this.progressChanged.isEmpty()) { ++ Set toProcess = isConcurrent ? new HashSet<>(this.progressChanged) : this.progressChanged; ++ + for (AdvancementHolder advancementHolder : toProcess) { -+ if (this.visible.contains(advancementHolder)) { // Only include progress for visible advancements + if (this.visible.contains(advancementHolder)) { map.put(advancementHolder.id(), this.progress.get(advancementHolder)); } } - this.progressChanged.clear(); -+ if (useConcurrent) { -+ this.progressChangedConcurrent.removeAll(toProcess); // Remove processed items from concurrent set ++ if (isConcurrent) { ++ this.progressChanged.removeAll(toProcess); // Remove processed items from concurrent set + } else { -+ this.progressChanged.clear(); // Clear the regular set ++ this.progressChanged.clear(); + } + } + // Leaf end - SparklyPaper - parallel world ticking if (!map.isEmpty() || !set.isEmpty() || !set1.isEmpty()) { player.connection.send(new ClientboundUpdateAdvancementsPacket(this.isFirstPacket, set, set1, map, showAdvancements)); } -@@ -330,10 +349,13 @@ public class PlayerAdvancements { - AdvancementHolder advancementHolder = node.holder(); - if (visible) { - if (this.visible.add(advancementHolder)) { -- advancementOutput.add(advancementHolder); -+ // Leaf start - SparklyPaper - parallel world ticking -+ advancementOutput.add(advancementHolder); // Add to visible set for packet - if (this.progress.containsKey(advancementHolder)) { -- this.progressChanged.add(advancementHolder); -+ // If progress exists, mark it changed so the progress data is sent -+ (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? this.progressChangedConcurrent : this.progressChanged).add(advancementHolder); - } -+ // Leaf end - SparklyPaper - parallel world ticking - } - } else if (this.visible.remove(advancementHolder)) { - idOutput.add(advancementHolder.id()); diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java -index 8350d6f00764c792f5f4f2c2e91df6777cb06e0d..dd6e59fea9e8a9e7660649c491eec30e1055f459 100644 +index 8350d6f00764c792f5f4f2c2e91df6777cb06e0d..2139fc5c3c3e6b45e148d6f8798b9fbfcdef626c 100644 --- a/net/minecraft/server/dedicated/DedicatedServer.java +++ b/net/minecraft/server/dedicated/DedicatedServer.java @@ -198,6 +198,12 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface org.purpurmc.purpur.PurpurConfig.registerCommands(); // Purpur end - Purpur config files org.dreeam.leaf.command.LeafCommands.registerCommands(this); // Leaf - Leaf commands -+ // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) ++ // Leaf start - SparklyPaper - parallel world ticking + if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { -+ serverLevelTickingSemaphore = new java.util.concurrent.Semaphore(org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.threads); // SparklyPaper - parallel world ticking -+ DedicatedServer.LOGGER.info("Using {} permits for parallel world ticking", serverLevelTickingSemaphore.availablePermits()); // SparklyPaper - parallel world ticking ++ serverLevelTickingSemaphore = new java.util.concurrent.Semaphore(org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.threads); ++ DedicatedServer.LOGGER.info("Using {} permits for parallel world ticking", serverLevelTickingSemaphore.availablePermits()); + } -+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) ++ // Leaf end - SparklyPaper - parallel world ticking com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now // Gale start - Pufferfish - SIMD support diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index eaaa66c4d86d4ebda0acf8f1dbe8ecb55aa28285..3a6c894178829cec8daa08ea9f0294f7f39a8efe 100644 +index eaaa66c4d86d4ebda0acf8f1dbe8ecb55aa28285..908cd08e33fed1c4cd4bd34c3e63cbbe84ffead4 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -175,6 +175,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon // call mid-tick tasks for chunk system if ((i & 7) == 0) { -+ if (!org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) ++ if (!org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.level.getServer()).moonrise$executeMidTickTasks(); continue; } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 30bb5b9d021d01dea272313763b0748856ce28a6..1b8f9cc3de9778e556a0017b320bfba983750f0f 100644 +index 30bb5b9d021d01dea272313763b0748856ce28a6..f1ce73c615f1804c1f44f140dc17965877937078 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -180,7 +180,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -431,34 +387,49 @@ index 30bb5b9d021d01dea272313763b0748856ce28a6..1b8f9cc3de9778e556a0017b320bfba9 public final net.minecraft.world.level.storage.PrimaryLevelData serverLevelData; // CraftBukkit - type private int lastSpawnChunkRadius; - final EntityTickList entityTickList = new EntityTickList(); -+ final EntityTickList entityTickList = new EntityTickList(this); // SparklyPaper - parallel world ticking ++ final EntityTickList entityTickList = new EntityTickList(this); // Leaf - SparklyPaper - parallel world ticking private final ServerWaypointManager waypointManager; // Paper - rewrite chunk system private final GameEventDispatcher gameEventDispatcher; -@@ -207,6 +207,9 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -207,6 +207,11 @@ 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 ++ // Leaf start - SparklyPaper - parallel world ticking ++ public final java.util.concurrent.ExecutorService tickExecutor; ++ public final java.util.concurrent.ConcurrentLinkedQueue asyncReadRequestQueue; ++ private volatile boolean isShuttingDown = false; // Shutdown handling for async reads ++ // Leaf end - SparklyPaper - parallel world ticking // CraftBukkit start public final LevelStorageSource.LevelStorageAccess levelStorageAccess; -@@ -680,7 +683,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -680,7 +685,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe this.sleepStatus = new SleepStatus(); this.gameEventDispatcher = new GameEventDispatcher(this); this.randomSequences = Objects.requireNonNullElseGet(randomSequences, () -> this.getDataStorage().computeIfAbsent(RandomSequences.TYPE)); - this.waypointManager = new ServerWaypointManager(); -+ this.waypointManager = new ServerWaypointManager(this); // SparklyPaper - parallel world ticking ++ this.waypointManager = new ServerWaypointManager(this); // Leaf - SparklyPaper - parallel world ticking // Paper start - rewrite chunk system this.moonrise$setEntityLookup(new ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup((ServerLevel)(Object)this, ((ServerLevel)(Object)this).new EntityCallbacks())); this.chunkTaskScheduler = new ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler((ServerLevel)(Object)this); -@@ -698,8 +701,26 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -698,6 +703,15 @@ 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 ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ this.tickExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new org.dreeam.leaf.async.world.SparklyPaperServerLevelTickExecutorThreadFactory(getWorld().getName())); ++ this.asyncReadRequestQueue = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ } else { ++ this.tickExecutor = null; ++ this.asyncReadRequestQueue = null; ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + // Paper start +@@ -730,8 +744,131 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + return this.structureManager; } + // Leaf start - SparklyPaper - parallel world ticking - Shutdown handling for async reads @@ -474,17 +445,12 @@ index 30bb5b9d021d01dea272313763b0748856ce28a6..1b8f9cc3de9778e556a0017b320bfba9 + 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."); ++ if (clearedRequests > 0) { ++ MinecraftServer.LOGGER.info("PWT: Cleared {} pending async read requests for world {} during shutdown.", clearedRequests, this.getWorld().getName()); ++ } + } + // Leaf end - SparklyPaper - parallel world ticking - Shutdown handling for async reads + - // Paper start - @Override - public boolean hasChunk(int chunkX, int chunkZ) { -@@ -730,8 +751,112 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - return this.structureManager; - } - + // Leaf start - SparklyPaper - parallel world ticking + private void processAsyncReadRequests() { + // Only process if parallel ticking is enabled and buffering is active @@ -594,93 +560,83 @@ index 30bb5b9d021d01dea272313763b0748856ce28a6..1b8f9cc3de9778e556a0017b320bfba9 TickRateManager tickRateManager = this.tickRateManager(); boolean runsNormally = tickRateManager.runsNormally(); if (runsNormally) { -@@ -739,6 +864,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -739,6 +876,12 @@ 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) { ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && (++this.tickedBlocksOrFluids & 7L) != 0L) { // Execute mid-tick tasks if PWT is 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 -@@ -831,7 +964,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -831,6 +974,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Paper end - Prevent block entity and entity crashes } -- this.moonrise$midTickTasks(); // Paper - rewrite chunk system -+ if (!org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) -+ this.moonrise$midTickTasks(); // Paper - rewrite chunk system ++ if (!org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) + this.moonrise$midTickTasks(); // Paper - rewrite chunk system // Gale end - Airplane - remove lambda from ticking guard - copied from guardEntityTick } - } -@@ -1311,9 +1445,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1311,7 +1455,10 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe fluidState.tick(this, pos, blockState); } // Paper start - rewrite chunk system - if ((++this.tickedBlocksOrFluids & 7L) != 0L) { -- ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); + // Leaf start - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) + ++this.tickedBlocksOrFluids; + if (!org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && (this.tickedBlocksOrFluids & 7L) != 0L) { -+ this.server.moonrise$executeMidTickTasks(); ++ // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) + ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); } -+ // 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 - - } -@@ -1324,9 +1461,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1324,7 +1471,10 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe blockState.tick(this, pos, this.random); } // Paper start - rewrite chunk system - if ((++this.tickedBlocksOrFluids & 7L) != 0L) { -- ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); + // Leaf start - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) + ++this.tickedBlocksOrFluids; + if (!org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && (this.tickedBlocksOrFluids & 7L) != 0L) { -+ this.server.moonrise$executeMidTickTasks(); ++ // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) + ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); } -+ // 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 - - } -@@ -1591,6 +1731,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1591,6 +1741,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } private void addPlayer(ServerPlayer player) { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper - parallel world ticking mod (make configurable / async is no longer safe; schedule on main) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add player off-main"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add player off-main"); // Leaf - SparklyPaper - parallel world ticking (async is no longer safe; schedule on main; additional concurrency issues logs) Entity entity = this.getEntity(player.getUUID()); if (entity != null) { LOGGER.warn("Force-added player with duplicate UUID {}", player.getUUID()); -@@ -1603,7 +1745,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1603,7 +1754,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // CraftBukkit start private boolean addEntity(Entity entity, @Nullable org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { - org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot -+ // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add entity off-main"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) -+ else ++ // Leaf start - SparklyPaper - parallel world ticking ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add entity off-main"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ } else { + org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot -+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) ++ } ++ // Leaf end - SparklyPaper - parallel world ticking entity.generation = false; // Paper - Don't fire sync event during generation; Reset flag if it was added during a ServerLevel generation process // Paper start - extra debug info if (entity.valid) { diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index b8f5cc4540c4a992ae4cf5673886ce6107eb82a8..6c2b7818ff6535f73fcbb87ef25b2992656ce348 100644 +index b8f5cc4540c4a992ae4cf5673886ce6107eb82a8..b8c59df25999183985538ade4e58e01eb9b5b586 100644 --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java @@ -463,6 +463,8 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc } // Paper end - rewrite chunk system -+ public boolean hasTickedAtLeastOnceInNewWorld = false; // SparklyPaper - parallel world ticking (fixes bug in DreamResourceReset where the inventory is opened AFTER the player has changed worlds, if you click with the quick tp torch in a chest, because the inventory is opened AFTER the player has teleported) ++ public boolean hasTickedAtLeastOnceInNewWorld = false; // Leaf - SparklyPaper - parallel world ticking (fixes bug in DreamResourceReset where the inventory is opened AFTER the player has changed worlds, if you click with the quick tp torch in a chest, because the inventory is opened AFTER the player has teleported) + public ServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) { super(level, gameProfile); @@ -689,62 +645,60 @@ index b8f5cc4540c4a992ae4cf5673886ce6107eb82a8..6c2b7818ff6535f73fcbb87ef25b2992 @Override public void tick() { -+ hasTickedAtLeastOnceInNewWorld = true; // SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) ++ hasTickedAtLeastOnceInNewWorld = true; // Leaf - SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) // CraftBukkit start if (this.joining) { this.joining = false; -@@ -1438,6 +1441,8 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -1438,6 +1441,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc teleportTransition.postTeleportTransition().onTransition(this); return this; } else { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot change dimension of a player off-main, from world " + serverLevel.getWorld().getName() + " to world " + level.getWorld().getName()); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot change dimension of a player off-main, from world[" + serverLevel.getWorld().getName() + "] to world[" + level.getWorld().getName() + "]"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs) // CraftBukkit start /* this.isChangingDimension = true; -@@ -1784,6 +1789,12 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -1784,6 +1788,12 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc return OptionalInt.empty(); } else { // CraftBukkit start -+ // SparklyPaper start - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && !hasTickedAtLeastOnceInNewWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ MinecraftServer.LOGGER.warn("Ignoring request to open container " + abstractContainerMenu + " because we haven't ticked in the current world yet!", new Throwable()); ++ // Leaf start - SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && !hasTickedAtLeastOnceInNewWorld) { ++ MinecraftServer.LOGGER.warn("Ignoring request to open container {} because we haven't ticked in the current world yet!", abstractContainerMenu, new Throwable()); + return OptionalInt.empty(); + } -+ // SparklyPaper end - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) ++ // Leaf end - SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) this.containerMenu = abstractContainerMenu; // Moved up if (!this.isImmobile()) this.connection -@@ -1848,6 +1859,11 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc +@@ -1848,6 +1858,11 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc } @Override public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ // SparklyPaper start - parallel world ticking (debugging) -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.logContainerCreationStacktraces) { -+ MinecraftServer.LOGGER.warn("Closing " + this.getBukkitEntity().getName() + " inventory that was created at", this.containerMenu.containerCreationStacktrace); ++ // Leaf start - SparklyPaper - parallel world ticking (debugging) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.logContainerCreationStacktraces) { ++ MinecraftServer.LOGGER.warn("Closing {} inventory that was created at", this.getBukkitEntity().getName(), this.containerMenu.containerCreationStacktrace); + } -+ // SparklyPaper end - parallel world ticking (debugging) ++ // Leaf end - SparklyPaper - parallel world ticking (debugging) org.bukkit.craftbukkit.event.CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit // Paper end - Inventory close reason this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java -index 837fed1c7786d6d3397f70d910cace4cdf2223ce..f61fac2767d2085c10a3d34ec475bb07d625737d 100644 +index 837fed1c7786d6d3397f70d910cace4cdf2223ce..8f5d037fcd307b88abfae13e590e987200114b8a 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java -@@ -257,6 +257,8 @@ public abstract class PlayerList { +@@ -257,6 +257,7 @@ public abstract class PlayerList { // Leaves end - replay mod api public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie cookie) { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot place new player off-main"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot place new player off-main"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs) player.isRealPlayer = true; // Paper player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed GameProfile gameProfile = player.getGameProfile(); -@@ -852,6 +854,15 @@ public abstract class PlayerList { +@@ -852,6 +853,15 @@ public abstract class PlayerList { } public ServerPlayer respawn(ServerPlayer player, boolean keepInventory, Entity.RemovalReason reason, @Nullable org.bukkit.event.player.PlayerRespawnEvent.RespawnReason eventReason, @Nullable org.bukkit.Location location) { -+ // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) ++ // Leaf start - SparklyPaper - parallel world ticking + if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && !this.server.isSameThread()) { + // Respawning is a complex operation that modifies global player lists and can interact with multiple + // worlds. It must be executed on the main server thread to ensure thread safety. We block the @@ -752,116 +706,107 @@ index 837fed1c7786d6d3397f70d910cace4cdf2223ce..f61fac2767d2085c10a3d34ec475bb07 + org.bukkit.Location finalLocation = location; + return this.server.submit(() -> this.respawn(player, keepInventory, reason, eventReason, finalLocation)).join(); + } -+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) ++ // Leaf end - SparklyPaper - parallel world ticking player.stopRiding(); // CraftBukkit this.players.remove(player); this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot -@@ -863,6 +874,7 @@ public abstract class PlayerList { +@@ -863,6 +873,7 @@ public abstract class PlayerList { ServerPlayer serverPlayer = player; Level fromWorld = player.level(); player.wonGame = false; -+ serverPlayer.hasTickedAtLeastOnceInNewWorld = false; // SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) ++ serverPlayer.hasTickedAtLeastOnceInNewWorld = false; // Leaf - SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) // CraftBukkit end serverPlayer.connection = player.connection; serverPlayer.restoreFrom(player, keepInventory); diff --git a/net/minecraft/server/waypoints/ServerWaypointManager.java b/net/minecraft/server/waypoints/ServerWaypointManager.java -index f9e7532f86122a379692561a639a209a126e8bba..fab317d6c9a1c914f19bae11846cb5761bbee76e 100644 +index f9e7532f86122a379692561a639a209a126e8bba..7716c491a053db8b18aa23a4c2c768bc6971f9a9 100644 --- a/net/minecraft/server/waypoints/ServerWaypointManager.java +++ b/net/minecraft/server/waypoints/ServerWaypointManager.java -@@ -20,8 +20,17 @@ public class ServerWaypointManager implements WaypointManager players = new HashSet<>(); private final Table connections = HashBasedTable.create(); -+ // SparklyPaper start - parallel world ticking ++ // Leaf start - SparklyPaper - parallel world ticking + private final net.minecraft.server.level.ServerLevel level; + public ServerWaypointManager(net.minecraft.server.level.ServerLevel level) { + this.level = level; + } -+ // SparklyPaper end - parallel world ticking ++ // Leaf end - SparklyPaper - parallel world ticking + @Override public void trackWaypoint(WaypointTransmitter waypoint) { -+ 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.level, "Cannot track waypoints off-main"); // SparklyPaper - parallel world ticking ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, "Cannot track waypoints off-main"); // Leaf - SparklyPaper - parallel world ticking this.waypoints.add(waypoint); for (ServerPlayer serverPlayer : this.players) { -@@ -31,6 +40,8 @@ public class ServerWaypointManager implements WaypointManager map = Tables.transpose(this.connections).row(waypoint); SetView set = Sets.difference(this.players, map.keySet()); -@@ -47,12 +58,16 @@ public class ServerWaypointManager implements WaypointManager connection.disconnect()); Tables.transpose(this.connections).row(waypoint).clear(); this.waypoints.remove(waypoint); } public void addPlayer(ServerPlayer player) { -+ 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.level, "Cannot add player to waypoints off-main"); // SparklyPaper - parallel world ticking ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, "Cannot add player to waypoints off-main"); // Leaf - SparklyPaper - parallel world ticking this.players.add(player); for (WaypointTransmitter waypointTransmitter : this.waypoints) { -@@ -65,6 +80,8 @@ public class ServerWaypointManager implements WaypointManager map = this.connections.row(player); SetView set = Sets.difference(this.waypoints, map.keySet()); -@@ -78,6 +95,8 @@ public class ServerWaypointManager implements WaypointManager { connection.disconnect(); return true; -@@ -87,6 +106,8 @@ public class ServerWaypointManager implements WaypointManager { -@@ -122,6 +145,8 @@ public class ServerWaypointManager implements WaypointManager portalEntityTask = entity -> { -+ assert entity.portalProcess != null; -+ // Fix NPE when portalProcess becomes null before task execution -+ // Portal process was likely nulled out (e.g., expired) between scheduling and execution. -+ if (entity.portalProcess == null) { -+ return; -+ } -+ -+ if (entity.portalProcess.isParallelCancelledByPlugin()) { -+ entity.portalProcess = null; -+ return; - } +- } - } - -+ TeleportTransition portalDestination = entity.portalProcess.getPortalDestination(serverLevel, entity); -+ if (portalDestination != null) { -+ ServerLevel level = portalDestination.newLevel(); -+ if (entity instanceof ServerPlayer // CraftBukkit - always call event for players -+ || (level != null && (level.dimension() == serverLevel.dimension() || entity.canTeleport(serverLevel, level)))) { // CraftBukkit -+ entity.teleport(portalDestination); -+ } -+ } -+ // Add another null check here just in case teleport() somehow nulled it (defensive) -+ if (entity.portalProcess != null) { -+ entity.portalProcess.confirmParallelAsHandled(); -+ } -+ }; -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { -+ this.portalProcess.setParallelAsScheduled(); -+ this.getBukkitEntity().taskScheduler.schedule(portalEntityTask, entity -> {}, 0); -+ } else -+ portalEntityTask.accept(this); -+ // Leaf end - SparklyPaper - parallel world ticking mod (mark pending teleport to prevent clearing portal process) +- ++ handleTeleport(serverLevel); // Leaf - SparklyPaper - parallel world ticking } else if (this.portalProcess.hasExpired()) { this.portalProcess = null; } -@@ -4061,6 +4086,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -3492,6 +3484,56 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + } + ++ // Leaf start - SparklyPaper - parallel world ticking - mark pending teleport to prevent clearing portal process ++ private void handleTeleport(ServerLevel serverLevel) { ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ pwt$handleTeleport(serverLevel); ++ } else { ++ TeleportTransition portalDestination = this.portalProcess.getPortalDestination(serverLevel, this); ++ if (portalDestination != null) { ++ ServerLevel level = portalDestination.newLevel(); ++ if (this instanceof ServerPlayer // CraftBukkit - always call event for players ++ || (level != null && (level.dimension() == serverLevel.dimension() || this.canTeleport(serverLevel, level)))) { // CraftBukkit ++ this.teleport(portalDestination); ++ } ++ } ++ } ++ } ++ ++ private void pwt$handleTeleport(ServerLevel serverLevel) { ++ java.util.function.Consumer portalEntityTask = entity -> { ++ assert entity.portalProcess != null; ++ // Fix NPE when portalProcess becomes null before task execution ++ // Portal process was likely nulled out (e.g., expired) between scheduling and execution. ++ if (entity.portalProcess == null) { ++ return; ++ } ++ ++ if (entity.portalProcess.isParallelCancelledByPlugin()) { ++ entity.portalProcess = null; ++ return; ++ } ++ ++ TeleportTransition portalDestination = entity.portalProcess.getPortalDestination(serverLevel, entity); ++ if (portalDestination != null) { ++ ServerLevel level = portalDestination.newLevel(); ++ if (entity instanceof ServerPlayer // CraftBukkit - always call event for players ++ || (level != null && (level.dimension() == serverLevel.dimension() || entity.canTeleport(serverLevel, level)))) { // CraftBukkit ++ entity.teleport(portalDestination); ++ } ++ } ++ // Add another null check here just in case teleport() somehow nulled it (defensive) ++ if (entity.portalProcess != null) { ++ entity.portalProcess.confirmParallelAsHandled(); ++ } ++ }; ++ ++ this.portalProcess.setParallelAsScheduled(); ++ this.getBukkitEntity().taskScheduler.schedule(portalEntityTask, entity -> { ++ }, 0); ++ } ++ // Leaf start - SparklyPaper - parallel world ticking - mark pending teleport to prevent clearing portal process ++ + public int getDimensionChangingDelay() { + Entity firstPassenger = this.getFirstPassenger(); + return firstPassenger instanceof ServerPlayer ? firstPassenger.getDimensionChangingDelay() : 300; +@@ -4061,6 +4103,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } private Entity teleportCrossDimension(ServerLevel oldLevel, ServerLevel newLevel, TeleportTransition teleportTransition) { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(newLevel, "Cannot teleport entity to another world off-main, from world " + oldLevel.getWorld().getName() + " to world " + newLevel.getWorld().getName()); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(newLevel, "Cannot teleport entity to another world off-main, from world[" + oldLevel.getWorld().getName() + "] to world[" + newLevel.getWorld().getName() + "]"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs) List passengers = this.getPassengers(); List list = new ArrayList<>(passengers.size()); this.ejectPassengers(); diff --git a/net/minecraft/world/entity/PortalProcessor.java b/net/minecraft/world/entity/PortalProcessor.java -index 88b07fbb96b20124777889830afa480673629d43..f8bb32840129e57b7799f883cb4570d274520390 100644 +index 88b07fbb96b20124777889830afa480673629d43..94da2272f402b8269aaae2983f4cc54bdf07caa0 100644 --- a/net/minecraft/world/entity/PortalProcessor.java +++ b/net/minecraft/world/entity/PortalProcessor.java @@ -11,6 +11,7 @@ public class PortalProcessor { private BlockPos entryPosition; private int portalTime; private boolean insidePortalThisTick; -+ private ParallelPendingTeleportState pendingTeleport = ParallelPendingTeleportState.INACTIVE; // Leaf - SparklyPaper - parallel world ticking mod (prevent clearing portal process) ++ private ParallelPendingTeleportState pendingTeleport = ParallelPendingTeleportState.INACTIVE; // Leaf - SparklyPaper - parallel world ticking - prevent clearing portal process public PortalProcessor(Portal portal, BlockPos entryPosition) { this.portal = portal; @@ -934,7 +904,7 @@ index 88b07fbb96b20124777889830afa480673629d43..f8bb32840129e57b7799f883cb4570d2 } public boolean processPortalTeleportation(ServerLevel level, Entity entity, boolean canChangeDimensions) { -+ if (this.isParallelTeleportScheduled()) return false; // Leaf - SparklyPaper - parallel world ticking mod (prevent clearing portal process) ++ if (this.isParallelTeleportScheduled()) return false; // Leaf - SparklyPaper - parallel world ticking - prevent clearing portal process + if (!this.insidePortalThisTick) { this.decayTick(); @@ -944,7 +914,7 @@ index 88b07fbb96b20124777889830afa480673629d43..f8bb32840129e57b7799f883cb4570d2 public boolean hasExpired() { - return this.portalTime <= 0; -+ return !this.isParallelTeleportScheduled() && this.portalTime <= 0; // Leaf - SparklyPaper - parallel world ticking mod (prevent clearing portal process) ++ return !this.isParallelTeleportScheduled() && this.portalTime <= 0; // Leaf - SparklyPaper - parallel world ticking - prevent clearing portal process } public BlockPos getEntryPosition() { @@ -953,7 +923,7 @@ index 88b07fbb96b20124777889830afa480673629d43..f8bb32840129e57b7799f883cb4570d2 return this.portal == portal; } + -+ // Leaf start - SparklyPaper - parallel world ticking mod (prevent clearing portal process) ++ // Leaf start - SparklyPaper - parallel world ticking - prevent clearing portal process + public boolean isParallelTeleportPending() { + return this.pendingTeleport == ParallelPendingTeleportState.PENDING; + } @@ -983,13 +953,13 @@ index 88b07fbb96b20124777889830afa480673629d43..f8bb32840129e57b7799f883cb4570d2 + PENDING, + CANCELLED + } -+ // Leaf end - SparklyPaper - parallel world ticking mod (prevent clearing portal process) ++ // Leaf end - SparklyPaper - parallel world ticking - prevent clearing portal process } diff --git a/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java b/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java -index 3614551856c594f3c0cfee984fcf03fad672b007..f4577f908ca9f279b72d89e5b0822d34b6fb7dd1 100644 +index 3614551856c594f3c0cfee984fcf03fad672b007..2127bd4f9dfcbba0e073375d17399afce71c2060 100644 --- a/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java +++ b/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java -@@ -44,14 +44,34 @@ public class GoToPotentialJobSite extends Behavior { +@@ -44,15 +44,39 @@ public class GoToPotentialJobSite extends Behavior { Optional memory = entity.getBrain().getMemory(MemoryModuleType.POTENTIAL_JOB_SITE); memory.ifPresent(globalPos -> { BlockPos blockPos = globalPos.pos(); @@ -998,44 +968,48 @@ index 3614551856c594f3c0cfee984fcf03fad672b007..f4577f908ca9f279b72d89e5b0822d34 - PoiManager poiManager = level1.getPoiManager(); - if (poiManager.exists(blockPos, holder -> true)) { - poiManager.release(blockPos); -- } + // Leaf start - SparklyPaper - parallel world ticking -+ ServerLevel entityLevel = level; // Villager's current level -+ ServerLevel poiLevel = entityLevel.getServer().getLevel(globalPos.dimension()); // POI's actual level ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ ServerLevel entityLevel = level; // Villager's current level ++ ServerLevel poiLevel = entityLevel.getServer().getLevel(globalPos.dimension()); // POI's actual level + -+ if (poiLevel != null) { -+ Runnable poiOperationsTask = () -> { -+ PoiManager poiManager = poiLevel.getPoiManager(); -+ if (poiManager.exists(blockPos, holder -> true)) { -+ poiManager.release(blockPos); -+ } -+ }; ++ if (poiLevel != null) { ++ Runnable poiOperationsTask = () -> { ++ PoiManager poiManager = poiLevel.getPoiManager(); ++ if (poiManager.exists(blockPos, holder -> true)) { ++ poiManager.release(blockPos); ++ } ++ }; ++ ++ // DebugPackets.sendPoiTicketCountPacket uses the entity's level for its PoiManager context. ++ Runnable debugPacketTask = () -> DebugPackets.sendPoiTicketCountPacket(entityLevel, blockPos); + -+ // DebugPackets.sendPoiTicketCountPacket uses the entity's level for its PoiManager context. -+ Runnable debugPacketTask = () -> { -+ DebugPackets.sendPoiTicketCountPacket(entityLevel, blockPos); -+ }; - -- DebugPackets.sendPoiTicketCountPacket(level, blockPos); -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { // Added curly braces here + // Schedule POI operations on the POI's level thread, using POI's chunk coordinates for locality + poiLevel.moonrise$getChunkTaskScheduler().scheduleChunkTask(blockPos.getX() >> 4, blockPos.getZ() >> 4, poiOperationsTask, ca.spottedleaf.concurrentutil.util.Priority.BLOCKING); + // Schedule debug packet on the entity's level thread, using entity's chunk coordinates for locality + entityLevel.moonrise$getChunkTaskScheduler().scheduleChunkTask(entity.chunkPosition().x, entity.chunkPosition().z, debugPacketTask, ca.spottedleaf.concurrentutil.util.Priority.BLOCKING); + } ++ } else { ++ ServerLevel level1 = level.getServer().getLevel(globalPos.dimension()); ++ if (level1 != null) { ++ PoiManager poiManager = level1.getPoiManager(); ++ if (poiManager.exists(blockPos, holder -> true)) { ++ poiManager.release(blockPos); ++ } + +- DebugPackets.sendPoiTicketCountPacket(level, blockPos); ++ DebugPackets.sendPoiTicketCountPacket(level, blockPos); + } -+ else { // PWT disabled, run inline on current (entity's) thread -+ poiOperationsTask.run(); // This will use poiLevel's PoiManager but thread checks are permissive -+ debugPacketTask.run(); // This will use entityLevel's PoiManager -+ } -+ // Leaf end - SparklyPaper - parallel world ticking } ++ // Leaf end - SparklyPaper - parallel world ticking }); entity.getBrain().eraseMemory(MemoryModuleType.POTENTIAL_JOB_SITE); + } diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java -index 2b20cd647ec00b3503cf5634dfbe9b9a8f8edbc4..6ea510d1e039d599746940325d2d184cb7e8fb6f 100644 +index 2b20cd647ec00b3503cf5634dfbe9b9a8f8edbc4..bcce0c38f476966c9789506cb6a83e2e1ce24cc4 100644 --- a/net/minecraft/world/entity/npc/Villager.java +++ b/net/minecraft/world/entity/npc/Villager.java -@@ -794,13 +794,21 @@ public class Villager extends AbstractVillager implements ReputationEventHandler +@@ -794,13 +794,29 @@ public class Villager extends AbstractVillager implements ReputationEventHandler this.brain.getMemory(moduleType).ifPresent(pos -> { ServerLevel level = server.getLevel(pos.dimension()); if (level != null) { @@ -1045,9 +1019,20 @@ index 2b20cd647ec00b3503cf5634dfbe9b9a8f8edbc4..6ea510d1e039d599746940325d2d184c - if (type.isPresent() && biPredicate.test(this, type.get())) { - poiManager.release(pos.pos()); - DebugPackets.sendPoiTicketCountPacket(level, pos.pos()); -- } -+ // Leaf start - SparklyPaper - parallel world ticking mod (handling for releasing poi cross-dimension) -+ Runnable releasePoiTask = () -> { ++ // Leaf start - SparklyPaper - parallel world ticking - handling for releasing poi cross-dimension ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ Runnable releasePoiTask = () -> { ++ PoiManager poiManager = level.getPoiManager(); ++ Optional> type = poiManager.getType(pos.pos()); ++ BiPredicate> biPredicate = POI_MEMORIES.get(moduleType); ++ if (type.isPresent() && biPredicate.test(this, type.get())) { ++ poiManager.release(pos.pos()); ++ DebugPackets.sendPoiTicketCountPacket(level, pos.pos()); ++ } ++ }; ++ ++ level.moonrise$getChunkTaskScheduler().scheduleChunkTask(0, 0, releasePoiTask, ca.spottedleaf.concurrentutil.util.Priority.BLOCKING); ++ } else { + PoiManager poiManager = level.getPoiManager(); + Optional> type = poiManager.getType(pos.pos()); + BiPredicate> biPredicate = POI_MEMORIES.get(moduleType); @@ -1055,24 +1040,20 @@ index 2b20cd647ec00b3503cf5634dfbe9b9a8f8edbc4..6ea510d1e039d599746940325d2d184c + poiManager.release(pos.pos()); + DebugPackets.sendPoiTicketCountPacket(level, pos.pos()); + } -+ }; -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) -+ level.moonrise$getChunkTaskScheduler().scheduleChunkTask(0, 0, releasePoiTask, ca.spottedleaf.concurrentutil.util.Priority.BLOCKING); -+ else -+ releasePoiTask.run(); -+ // Leaf end - SparklyPaper - parallel world ticking mod (handling for releasing poi cross-dimension) + } ++ // Leaf end - SparklyPaper - parallel world ticking - handling for releasing poi cross-dimension } }); } diff --git a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java -index ebc7db0fc6e8fb8f4bd19945e61287b2ff61cdbc..4d6d843769e1ac9176fc1e90d429b8f0f2ec1f85 100644 +index ebc7db0fc6e8fb8f4bd19945e61287b2ff61cdbc..d1875d36ccdad96aabfe275237984f47cdc4d555 100644 --- a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java +++ b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java @@ -119,11 +119,13 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { Vec3 vec3 = this.oldPosition(); if (owner instanceof ServerPlayer serverPlayer) { if (serverPlayer.connection.isAcceptingMessages()) { -+ // Leaf start - SparklyPaper - parallel world ticking mod (handling for pearl teleportation cross-dimension) ++ // Leaf start - SparklyPaper - parallel world ticking - handling for pearl teleportation cross-dimension + java.util.function.Consumer teleportPlayerCrossDimensionTask = taskServerPlayer -> { // CraftBukkit start // Store pre teleportation position as the teleport has been moved up. @@ -1083,7 +1064,7 @@ index ebc7db0fc6e8fb8f4bd19945e61287b2ff61cdbc..4d6d843769e1ac9176fc1e90d429b8f0 if (serverPlayer1 == null) { this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT); return; -@@ -152,10 +154,16 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { +@@ -152,10 +154,18 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { if (serverPlayer1 != null) { serverPlayer1.resetFallDistance(); serverPlayer1.resetCurrentImpulseContext(); @@ -1093,35 +1074,37 @@ index ebc7db0fc6e8fb8f4bd19945e61287b2ff61cdbc..4d6d843769e1ac9176fc1e90d429b8f0 this.playSound(serverLevel, vec3); + }; -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) -+ serverPlayer.getBukkitEntity().taskScheduler.schedule(teleportPlayerCrossDimensionTask, entity -> {}, 0); -+ else ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ serverPlayer.getBukkitEntity().taskScheduler.schedule(teleportPlayerCrossDimensionTask, entity -> { ++ }, 0); ++ } else { + teleportPlayerCrossDimensionTask.accept(serverPlayer); -+ // Leaf end - SparklyPaper - parallel world ticking mod (handling for pearl teleportation cross-dimension) ++ } ++ // Leaf end - SparklyPaper - parallel world ticking - handling for pearl teleportation cross-dimension } } else { Entity entity = owner.teleport( diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java -index 4354aafdd29c397d1318ae71dc365e7ca0aa781c..6509ff50a1a40ebdbe544785011abd932c185771 100644 +index 4354aafdd29c397d1318ae71dc365e7ca0aa781c..89600b750e85011e6cf39b2d5559111d22735e9c 100644 --- a/net/minecraft/world/inventory/AbstractContainerMenu.java +++ b/net/minecraft/world/inventory/AbstractContainerMenu.java @@ -96,8 +96,14 @@ public abstract class AbstractContainerMenu { public void startOpen() {} // CraftBukkit end -+ public Throwable containerCreationStacktrace; // SparklyPaper - parallel world ticking (debugging) ++ public Throwable containerCreationStacktrace; // Leaf - SparklyPaper - parallel world ticking (debugging) protected AbstractContainerMenu(@Nullable MenuType menuType, int containerId) { -+ // SparklyPaper - parallel world ticking (debugging) -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.logContainerCreationStacktraces) { ++ // Leaf start - SparklyPaper - parallel world ticking (debugging) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.logContainerCreationStacktraces) { + this.containerCreationStacktrace = new Throwable(); + } -+ // SparklyPaper end - parallel world ticking (debugging) ++ // Leaf end - SparklyPaper - parallel world ticking (debugging) this.menuType = menuType; this.containerId = containerId; } diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java -index 8c713d90e81df61d65fa6770516afc4704bbbb6f..f5ca3d6b29b11475ac56cd206464577b2d85b7dc 100644 +index 8c713d90e81df61d65fa6770516afc4704bbbb6f..e17b3b10f665f7b3d5f607838723071a7e0bfc59 100644 --- a/net/minecraft/world/item/ItemStack.java +++ b/net/minecraft/world/item/ItemStack.java @@ -398,8 +398,8 @@ public final class ItemStack implements DataComponentHolder { @@ -1130,104 +1113,103 @@ index 8c713d90e81df61d65fa6770516afc4704bbbb6f..f5ca3d6b29b11475ac56cd206464577b org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(clickedPos, serverLevel.getWorld()); - org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; - net.minecraft.world.level.block.SaplingBlock.treeType = null; -+ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.getTreeTypeRT(); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) -+ net.minecraft.world.level.block.SaplingBlock.setTreeTypeRT(null); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) ++ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.getTreeTypeRT(); // Leaf - SparklyPaper - parallel world ticking ++ net.minecraft.world.level.block.SaplingBlock.setTreeTypeRT(null); // Leaf - SparklyPaper - parallel world ticking List blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values()); serverLevel.capturedBlockStates.clear(); org.bukkit.event.world.StructureGrowEvent structureEvent = null; diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index adf9d0bc98408477450bcee8628b91fda2b95c54..2087557416757b17ccdc24a59354eee67ce85627 100644 +index adf9d0bc98408477450bcee8628b91fda2b95c54..779ed937e35ebf10ce49a04bde31cd78de264ba4 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -166,6 +166,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl // Gale end - Gale configuration public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files -+ public io.papermc.paper.redstone.RedstoneWireTurbo turbo = new io.papermc.paper.redstone.RedstoneWireTurbo((net.minecraft.world.level.block.RedStoneWireBlock) net.minecraft.world.level.block.Blocks.REDSTONE_WIRE); // SparklyPaper - parallel world ticking (moved to world) ++ public final io.papermc.paper.redstone.RedstoneWireTurbo turbo; // Leaf - SparklyPaper - parallel world ticking - moved to world public static @Nullable BlockPos lastPhysicsProblem; // Spigot private int tileTickPosition; public final Map explosionDensityCache = new java.util.HashMap<>(); // Paper - Optimize explosions -@@ -1138,6 +1139,8 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl +@@ -968,6 +969,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl + // CraftBukkit end + this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new io.papermc.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : io.papermc.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray + this.entityLookup = new ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl.DefaultEntityLookup(this); // Paper - rewrite chunk system ++ this.turbo = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? new io.papermc.paper.redstone.RedstoneWireTurbo((net.minecraft.world.level.block.RedStoneWireBlock) net.minecraft.world.level.block.Blocks.REDSTONE_WIRE) : null; + } + + // Paper start - Cancel hit for vanished players +@@ -1138,6 +1140,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl @Override public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft) { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, pos, "Updating block asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, pos, "Updating block asynchronously"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs) // CraftBukkit start - tree generation if (this.captureTreeGeneration) { // Paper start - Protect Bedrock and End Portal/Frames from being destroyed -@@ -1516,9 +1519,12 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl +@@ -1516,7 +1519,10 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl } else if (runsNormally && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) { tickingBlockEntity.tick(); // Paper start - rewrite chunk system - if ((++tickedEntities & 7) == 0) { -- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks(); -+ // Leaf start - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) - do not bother with condition work / make configurable ++ // Leaf start - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) + ++tickedEntities; + if (!org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && (tickedEntities & 7) == 0) { -+ this.moonrise$midTickTasks(); ++ // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) + ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks(); } -+ // Leaf end - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) - do not bother with condition work / make configurable // Paper end - rewrite chunk system - } - } -@@ -1539,7 +1545,8 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl +@@ -1539,6 +1545,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Gale - Airplane - remove lambda from ticking guard - diff on change ServerLevel#tick // Paper end - Prevent block entity and entity crashes } -- this.moonrise$midTickTasks(); // Paper - rewrite chunk system // Gale - Airplane - remove lambda from ticking guard - diff on change ServerLevel#tick -+ if (!org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) -+ this.moonrise$midTickTasks(); // Paper - rewrite chunk system // Gale - Airplane - remove lambda from ticking guard - diff on change ServerLevel#tick ++ if (!org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) + this.moonrise$midTickTasks(); // Paper - rewrite chunk system // Gale - Airplane - remove lambda from ticking guard - diff on change ServerLevel#tick } - // Paper start - Option to prevent armor stands from doing entity lookups -@@ -1676,6 +1683,8 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl +@@ -1676,6 +1683,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl @Nullable @Override public BlockEntity getBlockEntity(BlockPos pos) { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThreadOrAsyncThread((ServerLevel) this, "Cannot read world asynchronously"); // SparklyPaper - parallel world ticking ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThreadOrAsyncThread((ServerLevel) this, "Cannot read world asynchronously"); // Leaf - SparklyPaper - parallel world ticking // Paper start - Perf: Optimize capturedTileEntities lookup net.minecraft.world.level.block.entity.BlockEntity blockEntity; if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(pos)) != null) { -@@ -1692,6 +1701,8 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl +@@ -1692,6 +1700,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl } public void setBlockEntity(BlockEntity blockEntity) { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel) this, "Cannot modify world asynchronously"); // SparklyPaper - parallel world ticking ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel) this, "Cannot modify world asynchronously"); // Leaf - SparklyPaper - parallel world ticking BlockPos blockPos = blockEntity.getBlockPos(); if (!this.isOutsideBuildHeight(blockPos)) { // CraftBukkit start -@@ -1776,6 +1787,8 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl +@@ -1776,6 +1785,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl @Override public List getEntities(@Nullable Entity entity, AABB boundingBox, Predicate predicate) { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs) List list = Lists.newArrayList(); // Paper start - rewrite chunk system -@@ -2098,8 +2111,15 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl +@@ -2098,8 +2108,15 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl public abstract RecipeAccess recipeAccess(); public BlockPos getBlockRandomPos(int x, int y, int z, int yMask) { - this.randValue = this.randValue * 3 + 1013904223; - int i = this.randValue >> 2; -+ // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) ++ // Leaf start - SparklyPaper - parallel world ticking + int i; -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) -+ i = this.random.nextInt() >> 2; // SparklyPaper - parallel world ticking -+ else { ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ i = this.random.nextInt() >> 2; ++ } else { + this.randValue = this.randValue * 3 + 1013904223; + i = this.randValue >> 2; + } -+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) ++ // Leaf end - SparklyPaper - parallel world ticking return new BlockPos(x + (i & 15), y + (i >> 16 & yMask), z + (i >> 8 & 15)); } diff --git a/net/minecraft/world/level/block/FungusBlock.java b/net/minecraft/world/level/block/FungusBlock.java -index 9711efb088bd0da9168e9bcd0496bd7caddd2974..0a8599191bee283ff3a318174f4334e330d466dd 100644 +index 9711efb088bd0da9168e9bcd0496bd7caddd2974..38a9d3c23b9ccdfd38e0454181bde762e00e4b3f 100644 --- a/net/minecraft/world/level/block/FungusBlock.java +++ b/net/minecraft/world/level/block/FungusBlock.java @@ -76,9 +76,9 @@ public class FungusBlock extends VegetationBlock implements BonemealableBlock { @@ -1235,15 +1217,15 @@ index 9711efb088bd0da9168e9bcd0496bd7caddd2974..0a8599191bee283ff3a318174f4334e3 .map((value) -> { if (this == Blocks.WARPED_FUNGUS) { - SaplingBlock.treeType = org.bukkit.TreeType.WARPED_FUNGUS; -+ SaplingBlock.setTreeTypeRT(org.bukkit.TreeType.WARPED_FUNGUS); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) ++ SaplingBlock.setTreeTypeRT(org.bukkit.TreeType.WARPED_FUNGUS); // Leaf - SparklyPaper - parallel world ticking } else if (this == Blocks.CRIMSON_FUNGUS) { - SaplingBlock.treeType = org.bukkit.TreeType.CRIMSON_FUNGUS; -+ SaplingBlock.setTreeTypeRT(org.bukkit.TreeType.CRIMSON_FUNGUS); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) ++ SaplingBlock.setTreeTypeRT(org.bukkit.TreeType.CRIMSON_FUNGUS); // Leaf - SparklyPaper - parallel world ticking } return value; }) diff --git a/net/minecraft/world/level/block/MushroomBlock.java b/net/minecraft/world/level/block/MushroomBlock.java -index d306f5f524dc64618df94c9783c2168dc561a5e3..2e0aec5327d92f89f38bbc393b3ba70597725445 100644 +index d306f5f524dc64618df94c9783c2168dc561a5e3..4d2ecaa6a7e29a1032bb8e8c20845fa106614a00 100644 --- a/net/minecraft/world/level/block/MushroomBlock.java +++ b/net/minecraft/world/level/block/MushroomBlock.java @@ -93,7 +93,7 @@ public class MushroomBlock extends VegetationBlock implements BonemealableBlock @@ -1251,50 +1233,49 @@ index d306f5f524dc64618df94c9783c2168dc561a5e3..2e0aec5327d92f89f38bbc393b3ba705 } else { level.removeBlock(pos, false); - SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM; // CraftBukkit -+ SaplingBlock.setTreeTypeRT((this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM); // CraftBukkit // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) ++ SaplingBlock.setTreeTypeRT((this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM); // CraftBukkit // Leaf - SparklyPaper - parallel world ticking if (optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos)) { return true; } else { diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java -index 1943a6aad888647953e2d9dbbeedb0bd81c6f9df..ccc98d71da3c1150878f96801fbf65bfa1a64472 100644 +index 1943a6aad888647953e2d9dbbeedb0bd81c6f9df..553b2f664940f01d0d3397179c6197123787dd94 100644 --- a/net/minecraft/world/level/block/RedStoneWireBlock.java +++ b/net/minecraft/world/level/block/RedStoneWireBlock.java -@@ -65,7 +65,10 @@ public class RedStoneWireBlock extends Block { - private final Function shapes; +@@ -66,6 +66,7 @@ public class RedStoneWireBlock extends Block { private final BlockState crossState; private final RedstoneWireEvaluator evaluator = new DefaultRedstoneWireEvaluator(this); -+ // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) public boolean shouldSignal = true; -+ private final ThreadLocal shouldSignalTL = ThreadLocal.withInitial(() -> true); -+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) ++ private final ThreadLocal shouldSignalTL = ThreadLocal.withInitial(() -> true); // Leaf - SparklyPaper - parallel world ticking @Override public MapCodec codec() { -@@ -283,7 +286,12 @@ public class RedStoneWireBlock extends Block { +@@ -283,7 +284,13 @@ public class RedStoneWireBlock extends Block { if (orientation != null) { source = pos.relative(orientation.getFront().getOpposite()); } - turbo.updateSurroundingRedstone(worldIn, pos, state, source); -+ // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) -+ worldIn.turbo.updateSurroundingRedstone(worldIn, pos, state, source); // SparklyPaper - parallel world ticking -+ else ++ // Leaf start - SparklyPaper - parallel world ticking ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ worldIn.turbo.updateSurroundingRedstone(worldIn, pos, state, source); ++ } else { + turbo.updateSurroundingRedstone(worldIn, pos, state, source); -+ // Leaf end - parallel world ticking mod (make configurable) ++ } ++ // Leaf end - parallel world ticking return; } updatePowerStrength(worldIn, pos, state, orientation, blockAdded); -@@ -311,7 +319,12 @@ public class RedStoneWireBlock extends Block { +@@ -311,7 +318,13 @@ public class RedStoneWireBlock extends Block { // [Space Walker] suppress shape updates and emit those manually to // bypass the new neighbor update stack. if (level.setBlock(pos, state, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS)) { - turbo.updateNeighborShapes(level, pos, state); -+ // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) -+ level.turbo.updateNeighborShapes(level, pos, state); // SparklyPaper - parallel world ticking -+ else ++ // Leaf start - SparklyPaper - parallel world ticking ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ level.turbo.updateNeighborShapes(level, pos, state); ++ } else { + turbo.updateNeighborShapes(level, pos, state); -+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) ++ } ++ // Leaf end - SparklyPaper - parallel world ticking } } } @@ -1306,7 +1287,7 @@ index 1943a6aad888647953e2d9dbbeedb0bd81c6f9df..ccc98d71da3c1150878f96801fbf65bf - int bestNeighborSignal = level.getBestNeighborSignal(pos); - this.shouldSignal = true; - return bestNeighborSignal; -+ // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) ++ // Leaf start - SparklyPaper - parallel world ticking + if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { + this.shouldSignalTL.set(false); + int bestNeighborSignal = level.getBestNeighborSignal(pos); @@ -1318,127 +1299,118 @@ index 1943a6aad888647953e2d9dbbeedb0bd81c6f9df..ccc98d71da3c1150878f96801fbf65bf + this.shouldSignal = true; + return bestNeighborSignal; + } -+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) ++ // Leaf end - SparklyPaper - parallel world ticking } private void checkCornerChangeAt(Level level, BlockPos pos) { -@@ -422,12 +444,21 @@ public class RedStoneWireBlock extends Block { +@@ -422,12 +444,18 @@ public class RedStoneWireBlock extends Block { @Override protected int getDirectSignal(BlockState blockState, BlockGetter blockAccess, BlockPos pos, Direction side) { - return !this.shouldSignal ? 0 : blockState.getSignal(blockAccess, pos, side); -+ // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) ++ // Leaf start - SparklyPaper - parallel world ticking + boolean signal = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? this.shouldSignalTL.get() : this.shouldSignal; + return !signal ? 0 : blockState.getSignal(blockAccess, pos, side); -+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) ++ // Leaf end - SparklyPaper - parallel world ticking } @Override protected int getSignal(BlockState blockState, BlockGetter blockAccess, BlockPos pos, Direction side) { - if (this.shouldSignal && side != Direction.DOWN) { -+ // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) -+ boolean signal; -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) -+ signal = this.shouldSignalTL.get(); -+ else -+ signal = this.shouldSignal; ++ // Leaf start - SparklyPaper - parallel world ticking ++ final boolean signal = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? this.shouldSignalTL.get() : this.shouldSignal; + if (signal && side != Direction.DOWN) { ++ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) int powerValue = blockState.getValue(POWER); if (powerValue == 0) { return 0; -@@ -441,6 +472,7 @@ public class RedStoneWireBlock extends Block { - return 0; - } - } -+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) - - protected static boolean shouldConnectTo(BlockState state) { - return shouldConnectTo(state, null); -@@ -459,7 +491,12 @@ public class RedStoneWireBlock extends Block { +@@ -459,7 +487,7 @@ public class RedStoneWireBlock extends Block { @Override protected boolean isSignalSource(BlockState state) { - return this.shouldSignal; -+ // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) -+ return this.shouldSignalTL.get(); -+ else -+ return this.shouldSignal; -+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) ++ return org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? this.shouldSignalTL.get() : this.shouldSignal; // Leaf - SparklyPaper - parallel world ticking } public static int getColorForPower(int power) { diff --git a/net/minecraft/world/level/block/SaplingBlock.java b/net/minecraft/world/level/block/SaplingBlock.java -index a22cb810622e0ae97bc2a0d6390d026d9482b783..3e7478e959da3a0191de6c76b80cbb9b625b6898 100644 +index a22cb810622e0ae97bc2a0d6390d026d9482b783..25b12f90403cf04b66610eb749c0775a012ffdd1 100644 --- a/net/minecraft/world/level/block/SaplingBlock.java +++ b/net/minecraft/world/level/block/SaplingBlock.java -@@ -26,6 +26,28 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { +@@ -26,6 +26,35 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { private static final VoxelShape SHAPE = Block.column(12.0, 0.0, 12.0); protected final TreeGrower treeGrower; public static @javax.annotation.Nullable org.bukkit.TreeType treeType; // CraftBukkit -+ public static final ThreadLocal treeTypeRT = new ThreadLocal<>(); // SparklyPaper - parallel world ticking (from Folia) ++ public static final ThreadLocal treeTypeRT = new ThreadLocal<>(); // Leaf - SparklyPaper - parallel world ticking (from Folia) + -+ // Leaf start - SparklyPaper - parallel world ticking mod ++ // Leaf start - SparklyPaper - parallel world ticking + // refer to original field in case plugins attempt to modify it + public static org.bukkit.TreeType getTreeTypeRT() { -+ org.bukkit.TreeType treeTypeRTCopy; -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && (treeTypeRTCopy = treeTypeRT.get()) != null) -+ return treeTypeRTCopy; -+ synchronized (SaplingBlock.class) { ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ org.bukkit.TreeType treeTypeRTCopy; ++ if ((treeTypeRTCopy = treeTypeRT.get()) != null) ++ return treeTypeRTCopy; ++ synchronized (SaplingBlock.class) { ++ return treeType; ++ } ++ } else { + return treeType; + } + } + + // update original field in case plugins attempt to access it + public static void setTreeTypeRT(org.bukkit.TreeType value) { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { + treeTypeRT.set(value); -+ synchronized (SaplingBlock.class) { ++ synchronized (SaplingBlock.class) { ++ treeType = value; ++ } ++ } else { + treeType = value; + } + } -+ // Leaf end - SparklyPaper - parallel world ticking mod ++ // Leaf end - SparklyPaper - parallel world ticking @Override public MapCodec codec() { -@@ -62,14 +84,14 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { +@@ -62,14 +91,14 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random); level.captureTreeGeneration = false; if (!level.capturedBlockStates.isEmpty()) { - org.bukkit.TreeType treeType = SaplingBlock.treeType; - SaplingBlock.treeType = null; -+ org.bukkit.TreeType treeTypeLocal = 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) ++ org.bukkit.TreeType treeTypeLocal = SaplingBlock.getTreeTypeRT(); // Leaf - SparklyPaper - parallel world ticking ++ SaplingBlock.setTreeTypeRT(null); // Leaf - SparklyPaper - parallel world ticking org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level.getWorld()); java.util.List blocks = new java.util.ArrayList<>(level.capturedBlockStates.values()); level.capturedBlockStates.clear(); org.bukkit.event.world.StructureGrowEvent event = null; - if (treeType != null) { - event = new org.bukkit.event.world.StructureGrowEvent(location, treeType, false, null, blocks); -+ if (treeTypeLocal != null) { // Leaf - SparklyPaper - parallel world ticking mod (fix missing stuff from original sparkly paper) -+ event = new org.bukkit.event.world.StructureGrowEvent(location, treeTypeLocal, false, null, blocks); // Leaf - SparklyPaper - parallel world ticking mod (fix missing stuff from original sparkly paper) ++ if (treeTypeLocal != null) { // Leaf - SparklyPaper - parallel world ticking - fix missing stuff from original sparkly paper ++ event = new org.bukkit.event.world.StructureGrowEvent(location, treeTypeLocal, false, null, blocks); // Leaf - SparklyPaper - parallel world ticking - fix missing stuff from original sparkly paper org.bukkit.Bukkit.getPluginManager().callEvent(event); } if (event == null || !event.isCancelled()) { diff --git a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java -index 5a094257a31f0500278a706a418e1697f8810ffb..dd1343cd1e7fe007ddf47d654653eb2fbf91bcdf 100644 +index 5a094257a31f0500278a706a418e1697f8810ffb..0fac869e152c0895e69b9b8913733be575dae14b 100644 --- a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +++ b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java @@ -76,6 +76,12 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co } public static boolean canUnlock(Player player, LockCode code, Component displayName, @Nullable BlockEntity blockEntity) { -+ // SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != serverPlayer.level()) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ net.minecraft.server.MinecraftServer.LOGGER.warn("Player " + serverPlayer.getScoreboardName() + " (" + serverPlayer.getStringUUID() + ") attempted to open a BlockEntity @ " + blockEntity.getLevel().getWorld().getName() + " " + blockEntity.getBlockPos().getX() + ", " + blockEntity.getBlockPos().getY() + ", " + blockEntity.getBlockPos().getZ() + " while they were in a different world " + serverPlayer.level().getWorld().getName() + " than the block themselves!"); ++ // Leaf start - SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != serverPlayer.level()) { ++ net.minecraft.server.MinecraftServer.LOGGER.warn("Player {} ({}) attempted to open a BlockEntity @ {} {}, {}, {} while they were in a different world {} than the block themselves!", serverPlayer.getScoreboardName(), serverPlayer.getStringUUID(), blockEntity.getLevel().getWorld().getName(), blockEntity.getBlockPos().getX(), blockEntity.getBlockPos().getY(), blockEntity.getBlockPos().getZ(), serverPlayer.level().getWorld().getName()); + return false; + } -+ // SparklyPaper end - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) ++ // Leaf end - SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != null && blockEntity.getLevel().getBlockEntity(blockEntity.getBlockPos()) == blockEntity) { final org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(blockEntity.getLevel(), blockEntity.getBlockPos()); net.kyori.adventure.text.Component lockedMessage = net.kyori.adventure.text.Component.translatable("container.isLocked", io.papermc.paper.adventure.PaperAdventure.asAdventure(displayName)); diff --git a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java -index 83c811eb5e493fa6630f16c206787f227fde089b..30f73951d7fe69be0f094a70d4e28f08f715fbc0 100644 +index 83c811eb5e493fa6630f16c206787f227fde089b..ae119e98e50d033ba76bb035fa184956dec6caa7 100644 --- a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java +++ b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java @@ -43,9 +43,9 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi @@ -1446,173 +1418,167 @@ index 83c811eb5e493fa6630f16c206787f227fde089b..30f73951d7fe69be0f094a70d4e28f08 public static void serverTick(Level level, BlockPos pos, BlockState state, SculkCatalystBlockEntity sculkCatalyst) { - org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = sculkCatalyst.getBlockPos(); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. -+ org.bukkit.craftbukkit.event.CraftEventFactory.setSourceBlockOverrideRT(pos); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) ++ org.bukkit.craftbukkit.event.CraftEventFactory.setSourceBlockOverrideRT(pos); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. // Leaf - SparklyPaper - parallel world ticking sculkCatalyst.catalystListener.getSculkSpreader().updateCursors(level, pos, level.getRandom(), true); - org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = null; // CraftBukkit -+ org.bukkit.craftbukkit.event.CraftEventFactory.setSourceBlockOverrideRT(null); // CraftBukkit // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) ++ org.bukkit.craftbukkit.event.CraftEventFactory.setSourceBlockOverrideRT(null); // CraftBukkit // Leaf - SparklyPaper - parallel world ticking } @Override diff --git a/net/minecraft/world/level/block/grower/TreeGrower.java b/net/minecraft/world/level/block/grower/TreeGrower.java -index d23f255de9208f42125fa358a9e8194c984fe4d3..c3acc0a875c0bf697b6b101051d80b3457431b92 100644 +index d23f255de9208f42125fa358a9e8194c984fe4d3..e48fefd6063f5eb1235ef98c65f78d892d1342ca 100644 --- a/net/minecraft/world/level/block/grower/TreeGrower.java +++ b/net/minecraft/world/level/block/grower/TreeGrower.java @@ -203,55 +203,57 @@ public final class TreeGrower { // CraftBukkit start private void setTreeType(Holder> feature) { -+ org.bukkit.TreeType treeType; // SparklyPaper - parallel world ticking ++ org.bukkit.TreeType treeType; // Leaf - SparklyPaper - parallel world ticking if (feature.is(TreeFeatures.OAK) || feature.is(TreeFeatures.OAK_BEES_005)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TREE; -+ treeType = org.bukkit.TreeType.TREE; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.TREE; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.HUGE_RED_MUSHROOM)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.RED_MUSHROOM; -+ treeType = org.bukkit.TreeType.RED_MUSHROOM; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.RED_MUSHROOM; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.HUGE_BROWN_MUSHROOM)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BROWN_MUSHROOM; -+ treeType = org.bukkit.TreeType.BROWN_MUSHROOM; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.BROWN_MUSHROOM; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.JUNGLE_TREE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.COCOA_TREE; -+ treeType = org.bukkit.TreeType.COCOA_TREE; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.COCOA_TREE; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.JUNGLE_TREE_NO_VINE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SMALL_JUNGLE; -+ treeType = org.bukkit.TreeType.SMALL_JUNGLE; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.SMALL_JUNGLE; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.PINE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_REDWOOD; -+ treeType = org.bukkit.TreeType.TALL_REDWOOD; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.TALL_REDWOOD; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.SPRUCE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.REDWOOD; -+ treeType = org.bukkit.TreeType.REDWOOD; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.REDWOOD; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.ACACIA)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.ACACIA; -+ treeType = org.bukkit.TreeType.ACACIA; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.ACACIA; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.BIRCH) || feature.is(TreeFeatures.BIRCH_BEES_005)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIRCH; -+ treeType = org.bukkit.TreeType.BIRCH; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.BIRCH; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.SUPER_BIRCH_BEES_0002)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_BIRCH; -+ treeType = org.bukkit.TreeType.TALL_BIRCH; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.TALL_BIRCH; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.SWAMP_OAK)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SWAMP; -+ treeType = org.bukkit.TreeType.SWAMP; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.SWAMP; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.FANCY_OAK) || feature.is(TreeFeatures.FANCY_OAK_BEES_005)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIG_TREE; -+ treeType = org.bukkit.TreeType.BIG_TREE; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.BIG_TREE; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.JUNGLE_BUSH)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE_BUSH; -+ treeType = org.bukkit.TreeType.JUNGLE_BUSH; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.JUNGLE_BUSH; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.DARK_OAK)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.DARK_OAK; -+ treeType = org.bukkit.TreeType.DARK_OAK; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.DARK_OAK; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.MEGA_SPRUCE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_REDWOOD; -+ treeType = org.bukkit.TreeType.MEGA_REDWOOD; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.MEGA_REDWOOD; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.MEGA_PINE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_PINE; -+ treeType = org.bukkit.TreeType.MEGA_PINE; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.MEGA_PINE; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.MEGA_JUNGLE_TREE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE; -+ treeType = org.bukkit.TreeType.JUNGLE; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.JUNGLE; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.AZALEA_TREE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.AZALEA; -+ treeType = org.bukkit.TreeType.AZALEA; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.AZALEA; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.MANGROVE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MANGROVE; -+ treeType = org.bukkit.TreeType.MANGROVE; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.MANGROVE; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.TALL_MANGROVE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_MANGROVE; -+ treeType = org.bukkit.TreeType.TALL_MANGROVE; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.TALL_MANGROVE; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.CHERRY) || feature.is(TreeFeatures.CHERRY_BEES_005)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.CHERRY; -+ treeType = org.bukkit.TreeType.CHERRY; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.CHERRY; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.PALE_OAK) || feature.is(TreeFeatures.PALE_OAK_BONEMEAL)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK; -+ treeType = org.bukkit.TreeType.PALE_OAK; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.PALE_OAK; // Leaf - SparklyPaper - parallel world ticking } else if (feature.is(TreeFeatures.PALE_OAK_CREAKING)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK_CREAKING; -+ treeType = org.bukkit.TreeType.PALE_OAK_CREAKING; // SparklyPaper - parallel world ticking ++ treeType = org.bukkit.TreeType.PALE_OAK_CREAKING; // Leaf - SparklyPaper - parallel world ticking } else { throw new IllegalArgumentException("Unknown tree generator " + feature); } -+ net.minecraft.world.level.block.SaplingBlock.setTreeTypeRT(treeType); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) ++ net.minecraft.world.level.block.SaplingBlock.setTreeTypeRT(treeType); // Leaf - SparklyPaper - parallel world ticking } // CraftBukkit end } diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java -index 65442f9ab1528fd1b736963bc51f21fd6a0781a0..7538f9c84e8463502f4fa1b4a84a4ac84a11e87d 100644 +index 65442f9ab1528fd1b736963bc51f21fd6a0781a0..24baf9fd222b700a17e064cf61fc3c259d9ae528 100644 --- a/net/minecraft/world/level/chunk/LevelChunk.java +++ b/net/minecraft/world/level/chunk/LevelChunk.java -@@ -401,6 +401,8 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p +@@ -401,6 +401,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p @Nullable @Override public BlockState setBlockState(BlockPos pos, BlockState state, int flags) { -+ 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.level, pos, "Updating block asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, pos, "Updating block asynchronously"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs) int y = pos.getY(); LevelChunkSection section = this.getSection(this.getSectionIndex(y)); boolean hasOnlyAir = section.hasOnlyAir(); diff --git a/net/minecraft/world/level/entity/EntityTickList.java b/net/minecraft/world/level/entity/EntityTickList.java -index dec51066fc3f57b7bdc56195313c219f45a7fbee..359ead3bd32862206bccb4504ad5b4d027dba7a9 100644 +index dec51066fc3f57b7bdc56195313c219f45a7fbee..da533f61b97b4e08cbf069109f484d1b7d2e5e8f 100644 --- a/net/minecraft/world/level/entity/EntityTickList.java +++ b/net/minecraft/world/level/entity/EntityTickList.java -@@ -11,16 +11,36 @@ import net.minecraft.world.entity.Entity; +@@ -11,16 +11,32 @@ import net.minecraft.world.entity.Entity; public class EntityTickList { public final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system // Pufferfish - private->public -+ // Leaf start - SparklyPaper - parallel world ticking mod -+ // preserve original constructor ++ // Leaf start - SparklyPaper - parallel world ticking ++ // Used to track async entity additions/removals/loops ++ @Nullable ++ private final net.minecraft.server.level.ServerLevel serverLevel; ++ + public EntityTickList() { + this(null); + } -+ // Leaf end - SparklyPaper - parallel world ticking mod + -+ // SparklyPaper start - parallel world ticking -+ // Used to track async entity additions/removals/loops -+ @Nullable // Leaf - SparklyPaper - parallel world ticking mod (preserve original constructor) -+ private final net.minecraft.server.level.ServerLevel serverLevel; -+ public EntityTickList(@Nullable net.minecraft.server.level.ServerLevel serverLevel) { // Leaf - SparklyPaper - parallel world ticking mod (preserve original constructor) ++ public EntityTickList(@Nullable net.minecraft.server.level.ServerLevel serverLevel) { + this.serverLevel = serverLevel; + } -+ // SparklyPaper end - parallel world ticking ++ // Leaf end - SparklyPaper - parallel world ticking + private void ensureActiveIsNotIterated() { // Paper - rewrite chunk system } public void add(Entity entity) { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist addition"); // Paper // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist addition"); // Paper // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs) this.ensureActiveIsNotIterated(); this.entities.add(entity); // Paper - rewrite chunk system } public void remove(Entity entity) { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist removal"); // Paper // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist removal"); // Paper // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs) this.ensureActiveIsNotIterated(); this.entities.remove(entity); // Paper - rewrite chunk system } -@@ -30,6 +50,8 @@ public class EntityTickList { +@@ -30,6 +46,7 @@ public class EntityTickList { } public void forEach(Consumer entity) { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverLevel, "Asynchronous entity ticklist iteration"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverLevel, "Asynchronous entity ticklist iteration"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs) // Paper start - rewrite chunk system // To ensure nothing weird happens with dimension travelling, do not iterate over new entries... // (by dfl iterator() is configured to not iterate over new entries) diff --git a/net/minecraft/world/level/saveddata/maps/MapIndex.java b/net/minecraft/world/level/saveddata/maps/MapIndex.java -index 06025d79cc2297119b22224d777aca79f9d3d9c1..8e70deae0ff70d21e31f54410e3886be0280575c 100644 +index 06025d79cc2297119b22224d777aca79f9d3d9c1..9c1fa292637f867db1ca2d964f912579e434f750 100644 --- a/net/minecraft/world/level/saveddata/maps/MapIndex.java +++ b/net/minecraft/world/level/saveddata/maps/MapIndex.java @@ -23,8 +23,10 @@ public class MapIndex extends SavedData { } public MapId getNextMapId() { -+ synchronized (TYPE) { // SparklyPaper start - parallel world ticking ++ synchronized (TYPE) { // Leaf start - SparklyPaper - parallel world ticking MapId mapId = new MapId(++this.lastMapId); this.setDirty(); return mapId; -+ } // SparklyPaper end - parallel world ticking ++ } // Leaf end - SparklyPaper - parallel world ticking } } diff --git a/leaf-server/minecraft-patches/features/0201-SparklyPaper-Track-each-world-MSPT.patch b/leaf-server/minecraft-patches/features/0201-SparklyPaper-Track-each-world-MSPT.patch index ba8d0557..fb8f382f 100644 --- a/leaf-server/minecraft-patches/features/0201-SparklyPaper-Track-each-world-MSPT.patch +++ b/leaf-server/minecraft-patches/features/0201-SparklyPaper-Track-each-world-MSPT.patch @@ -6,11 +6,11 @@ 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 b81de5b278c43a404d0f6b96cf134b401f137ab3..3531e37b784b4d61660c66cd92bc609884f78a91 100644 +index 51efe9aadb377c35d6ed2b7eadead3c9e1c745f8..d3c6f7f78d895cca4abe1da79abb595c51449afc 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -1687,7 +1687,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop fluid, final double flowScale) { @@ -26,7 +26,7 @@ index 45bc4aa35fda48c816e08ec8bacfb11a5c792b27..7160a31e2d6b4315d3aa46b857a7a175 final AABB boundingBox = this.getBoundingBox().deflate(1.0E-3); final Level world = this.level; -@@ -4818,7 +4815,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4834,7 +4831,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { diff --git a/leaf-server/minecraft-patches/features/0222-Paper-PR-Add-ticket-on-player-join-to-avoid-chunk-lo.patch b/leaf-server/minecraft-patches/features/0222-Paper-PR-Add-ticket-on-player-join-to-avoid-chunk-lo.patch index 89584fa8..07804a46 100644 --- a/leaf-server/minecraft-patches/features/0222-Paper-PR-Add-ticket-on-player-join-to-avoid-chunk-lo.patch +++ b/leaf-server/minecraft-patches/features/0222-Paper-PR-Add-ticket-on-player-join-to-avoid-chunk-lo.patch @@ -27,10 +27,10 @@ index 525f29f3ecd7eb12e3d7447898005d3a6d62c093..24f0d13922748eb8a96fd83f14204984 public static final int GENERATED_TICKET_LEVEL = ChunkHolderManager.FULL_LOADED_TICKET_LEVEL; public static final int LOADED_TICKET_LEVEL = ChunkTaskScheduler.getTicketLevel(ChunkStatus.EMPTY); diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java -index f61fac2767d2085c10a3d34ec475bb07d625737d..368d5c9b17689ddb885ca967cc141b405fed8cd8 100644 +index 26a0f9aa63596572f10de5e0b435660e379bcf78..0261568809e7e67431a41dbb78bfb4349ee2b21a 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java -@@ -441,6 +441,13 @@ public abstract class PlayerList { +@@ -440,6 +440,13 @@ public abstract class PlayerList { // this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player))); // CraftBukkit - replaced with loop below // Paper start - Fire PlayerJoinEvent when Player is actually ready; correctly register player BEFORE PlayerJoinEvent, so the entity is valid and doesn't require tick delay hacks player.supressTrackerForLogin = true; diff --git a/leaf-server/minecraft-patches/features/0225-Protocol-Core.patch b/leaf-server/minecraft-patches/features/0225-Protocol-Core.patch index e2450ac0..f05fa585 100644 --- a/leaf-server/minecraft-patches/features/0225-Protocol-Core.patch +++ b/leaf-server/minecraft-patches/features/0225-Protocol-Core.patch @@ -22,10 +22,10 @@ index 56fd1ed7ccaf96e7eedea60fbdbf7f934939d563..d2f522ea6d0a209496848af073c9af1c } diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 3531e37b784b4d61660c66cd92bc609884f78a91..f89abb17dd84a58791068bbbf8fc64f4def644bf 100644 +index d3c6f7f78d895cca4abe1da79abb595c51449afc..7306a3273adf5bfa8ebdec42b7aedf3656fb5850 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -1804,6 +1804,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0) { this.awardStat(Stats.WALK_UNDER_WATER_ONE_CM, rounded); diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index d62d82dfab6fff45ca53c38510443adb85caeb6a..1765d79fd2102c0df7fb6b686872a84de2fdc38f 100644 +index acd8041cd984ff1d4c424d9b3f4c1a21022ec6d4..7c625f9fee3887326518e8e38cb4f04e72a4dbf7 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -288,7 +288,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess diff --git a/leaf-server/minecraft-patches/features/0243-optimize-mob-spawning.patch b/leaf-server/minecraft-patches/features/0243-optimize-mob-spawning.patch index e911364b..ed098d49 100644 --- a/leaf-server/minecraft-patches/features/0243-optimize-mob-spawning.patch +++ b/leaf-server/minecraft-patches/features/0243-optimize-mob-spawning.patch @@ -30,7 +30,7 @@ index 5c369b3d94e369c3f240821ad90b9d96223f24ca..9803c395fce103cb7bc746f43a017ff9 } // Paper end - Optional per player mob spawns diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index 3a6c894178829cec8daa08ea9f0294f7f39a8efe..2721b999dcfcf1cd5a0919221f2d94da4c93a6e7 100644 +index 908cd08e33fed1c4cd4bd34c3e63cbbe84ffead4..f5b58c181726536bedabd4b6f64769e312ef4257 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -70,11 +70,11 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -188,10 +188,10 @@ index 3a6c894178829cec8daa08ea9f0294f7f39a8efe..2721b999dcfcf1cd5a0919221f2d94da } } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 59f6fe6fbac2da1ae2485cb7a50ca59f9389cb43..2b132c1430b8195a24269c8e5ca7e57457f174c5 100644 +index 209d18228aa28b063f8f3e60df6a3e2c5aa770fc..549b1112e246c2c01601c13b35aa31f89c813956 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -1092,6 +1092,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1102,6 +1102,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe private int currentIceAndSnowTick = 0; protected void resetIceAndSnowTick() { this.currentIceAndSnowTick = this.simpleRandom.nextInt(16); } // Gale - Airplane - optimize random calls in chunk ticking public final org.dreeam.leaf.world.DespawnMap despawnMap = new org.dreeam.leaf.world.DespawnMap(paperConfig()); // Leaf - optimize despawn diff --git a/leaf-server/minecraft-patches/features/0248-Use-UUID-for-cure-reputation.patch b/leaf-server/minecraft-patches/features/0248-Use-UUID-for-cure-reputation.patch index 9823c98c..fd7d4087 100644 --- a/leaf-server/minecraft-patches/features/0248-Use-UUID-for-cure-reputation.patch +++ b/leaf-server/minecraft-patches/features/0248-Use-UUID-for-cure-reputation.patch @@ -22,10 +22,10 @@ index 22c1545a0329d56e0ec41ae4da1e1922aa1f9737..e89e4c26c40cbb3ef002022f22886d5f } diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java -index 6ea510d1e039d599746940325d2d184cb7e8fb6f..a4da72da9be1d298e271291fd50c7b7c067811cc 100644 +index bcce0c38f476966c9789506cb6a83e2e1ce24cc4..dfc959ee560b4c0c6d27067ac34be05a79f275c0 100644 --- a/net/minecraft/world/entity/npc/Villager.java +++ b/net/minecraft/world/entity/npc/Villager.java -@@ -1092,6 +1092,21 @@ public class Villager extends AbstractVillager implements ReputationEventHandler +@@ -1100,6 +1100,21 @@ public class Villager extends AbstractVillager implements ReputationEventHandler } } diff --git a/leaf-server/minecraft-patches/features/0250-optimize-random-tick.patch b/leaf-server/minecraft-patches/features/0250-optimize-random-tick.patch index 59d4b8cf..a5fc287b 100644 --- a/leaf-server/minecraft-patches/features/0250-optimize-random-tick.patch +++ b/leaf-server/minecraft-patches/features/0250-optimize-random-tick.patch @@ -5,7 +5,7 @@ Subject: [PATCH] optimize random tick diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index 2721b999dcfcf1cd5a0919221f2d94da4c93a6e7..2a1a6a0306b781e9ae5c0e3261cb740f37be4a8c 100644 +index f5b58c181726536bedabd4b6f64769e312ef4257..2847aa24cca82b1c66b69600ddcb5dbdadec5b9d 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -664,7 +664,13 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -24,10 +24,10 @@ index 2721b999dcfcf1cd5a0919221f2d94da4c93a6e7..2a1a6a0306b781e9ae5c0e3261cb740f this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies); } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 2b132c1430b8195a24269c8e5ca7e57457f174c5..bb67edb1a429b46efc9de320f672c86462f35caf 100644 +index 1d5195a17d1f5a934058610d71101a3775214769..e28d5c70f77d68e465f7cca74cde04e278dbeb01 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -1093,6 +1093,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1103,6 +1103,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe public final org.dreeam.leaf.world.DespawnMap despawnMap = new org.dreeam.leaf.world.DespawnMap(paperConfig()); // Leaf - optimize despawn public final org.dreeam.leaf.world.NatureSpawnChunkMap natureSpawnChunkMap = new org.dreeam.leaf.world.NatureSpawnChunkMap(); // Leaf - optimize mob spawning @@ -36,7 +36,7 @@ index 2b132c1430b8195a24269c8e5ca7e57457f174c5..bb67edb1a429b46efc9de320f672c864 final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = this.simpleRandom; // Paper - optimise random ticking // Leaf - Faster random generator - upcasting ChunkPos pos = chunk.getPos(); diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java -index a3674ddb883eecb255279375a5e2eece7e016c0f..c3ecdd80efad340c9fa0ea6913db9a3362b53ad4 100644 +index af4abc9bef1b147bd6435923177968258873113e..629ee839c152217c64ff1fead3d112d4cb06e21f 100644 --- a/net/minecraft/world/level/chunk/LevelChunk.java +++ b/net/minecraft/world/level/chunk/LevelChunk.java @@ -152,6 +152,10 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p @@ -50,7 +50,7 @@ index a3674ddb883eecb255279375a5e2eece7e016c0f..c3ecdd80efad340c9fa0ea6913db9a33 public LevelChunk(Level level, ChunkPos pos) { this(level, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null); } -@@ -416,6 +420,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p +@@ -415,6 +419,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p if (blockState == state) { return null; } else { diff --git a/leaf-server/minecraft-patches/features/0255-optimize-waypoint.patch b/leaf-server/minecraft-patches/features/0255-optimize-waypoint.patch index 341b5e43..ea0d2ebe 100644 --- a/leaf-server/minecraft-patches/features/0255-optimize-waypoint.patch +++ b/leaf-server/minecraft-patches/features/0255-optimize-waypoint.patch @@ -5,10 +5,10 @@ Subject: [PATCH] optimize waypoint diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 1765d79fd2102c0df7fb6b686872a84de2fdc38f..663dbcbec91f92a941cbd6d40053855ec2545981 100644 +index d50464e802455c39625701512930d6eb5f1da65b..984e1ba6adc13da1a9d8e901f008ded9b04e1224 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java -@@ -5117,7 +5117,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -5133,7 +5133,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess int floor = Mth.floor(x); int floor1 = Mth.floor(y); int floor2 = Mth.floor(z); @@ -18,7 +18,7 @@ index 1765d79fd2102c0df7fb6b686872a84de2fdc38f..663dbcbec91f92a941cbd6d40053855e this.blockPosition = new BlockPos(floor, floor1, floor2); this.inBlockState = null; if (SectionPos.blockToSectionCoord(floor) != this.chunkPosition.x || SectionPos.blockToSectionCoord(floor2) != this.chunkPosition.z) { -@@ -5126,7 +5127,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -5142,7 +5143,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } this.levelCallback.onMove(); diff --git a/leaf-server/minecraft-patches/features/0256-Paw-optimization.patch b/leaf-server/minecraft-patches/features/0256-Paw-optimization.patch index cbf42276..175a8575 100644 --- a/leaf-server/minecraft-patches/features/0256-Paw-optimization.patch +++ b/leaf-server/minecraft-patches/features/0256-Paw-optimization.patch @@ -100,7 +100,7 @@ index 4535858701b2bb232b9d2feb2af6551526232ddc..e65c62dbe4c1560ae153e4c4344e9194 - // Paper end - detailed watchdog information } diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index 2a1a6a0306b781e9ae5c0e3261cb740f37be4a8c..7e84b94a4602801e8cc713b28d0d93052589fd5e 100644 +index 2847aa24cca82b1c66b69600ddcb5dbdadec5b9d..bd4c98e9ec41a2bd608e2e2245c503be3171ceee 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -638,8 +638,10 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -117,10 +117,10 @@ index 2a1a6a0306b781e9ae5c0e3261cb740f37be4a8c..7e84b94a4602801e8cc713b28d0d9305 if (!org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled || _pufferfish_spawnCountsReady.get()) { diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index bb67edb1a429b46efc9de320f672c86462f35caf..062d0abf366c98803b92bd2169c945346845c45d 100644 +index edec4bf5f8818e242bda0cf19bedadb6532edb9f..78b252fcdd0079508d3811ba299b1faedd661c51 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -1496,13 +1496,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1506,13 +1506,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // Paper end - log detailed entity tick information public void tickNonPassenger(Entity entity) { @@ -134,7 +134,7 @@ index bb67edb1a429b46efc9de320f672c86462f35caf..062d0abf366c98803b92bd2169c94534 entity.setOldPosAndRot(); entity.tickCount++; entity.totalEntityAge++; // Paper - age-like counter for all entities -@@ -1515,13 +1509,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1525,13 +1519,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe for (Entity entity1 : entity.getPassengers()) { this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2 } @@ -149,7 +149,7 @@ index bb67edb1a429b46efc9de320f672c86462f35caf..062d0abf366c98803b92bd2169c94534 private void tickPassenger(Entity ridingEntity, Entity passengerEntity, final boolean isActive) { // Paper - EAR 2 diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 663dbcbec91f92a941cbd6d40053855ec2545981..e8128fbf13e3a7083825253361227625dc46eef2 100644 +index 984e1ba6adc13da1a9d8e901f008ded9b04e1224..d2a6f2bfa4370fee99b3f535724fc3ee18ce496f 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -1136,16 +1136,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @@ -200,7 +200,7 @@ index 663dbcbec91f92a941cbd6d40053855ec2545981..e8128fbf13e3a7083825253361227625 } private void applyMovementEmissionAndPlaySound(Entity.MovementEmission movementEmission, Vec3 movement, BlockPos pos, BlockState state) { -@@ -5011,9 +4985,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -5027,9 +5001,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public void setDeltaMovement(Vec3 deltaMovement) { @@ -210,7 +210,7 @@ index 663dbcbec91f92a941cbd6d40053855ec2545981..e8128fbf13e3a7083825253361227625 } public void addDeltaMovement(Vec3 addend) { -@@ -5111,9 +5083,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -5127,9 +5099,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } // Paper end - Block invalid positions and bounding box if (this.position.x != x || this.position.y != y || this.position.z != z) { @@ -313,7 +313,7 @@ index 010e9814490ffaa153df5b7865da17e2a84c7e82..d6750aec0f4695a99557beb92fda7a89 i = Math.min(i, getDistanceAt(level.getBlockState(mutableBlockPos)) + 1); if (i == 1) { diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java -index ccc98d71da3c1150878f96801fbf65bfa1a64472..a4e26381dcc831361c2d6bef6427d1d6eb7cef18 100644 +index 5b248fe2dc06f46c519b388a054776d4a0df7421..60d4399368752f9d3cfc8091ed08ca8ec1a31bcf 100644 --- a/net/minecraft/world/level/block/RedStoneWireBlock.java +++ b/net/minecraft/world/level/block/RedStoneWireBlock.java @@ -360,7 +360,7 @@ public class RedStoneWireBlock extends Block { diff --git a/leaf-server/minecraft-patches/features/0257-Fix-Paper-config-fixClimbingBypassingCrammingRule.patch b/leaf-server/minecraft-patches/features/0257-Fix-Paper-config-fixClimbingBypassingCrammingRule.patch index cdea1fbe..73ae4d08 100644 --- a/leaf-server/minecraft-patches/features/0257-Fix-Paper-config-fixClimbingBypassingCrammingRule.patch +++ b/leaf-server/minecraft-patches/features/0257-Fix-Paper-config-fixClimbingBypassingCrammingRule.patch @@ -19,10 +19,10 @@ index de22fce4f98a600a26d1d78f3bbc1c5f5a8b5bfa..f62898ec36cea977d67da087bf741fb5 // Leaf end - Only player pushable if (!pushableEntities.isEmpty()) { diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index ceba364a50fc96865cadf30674655a109baae06d..71f13ae7f3c4f65705dd278da8e4be55da9630c4 100644 +index 45260128dbeb29891fa80aab5256623def3bcabe..28e2c4f211de1a3a9d95342965699917db613782 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java -@@ -1908,7 +1908,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl +@@ -1905,7 +1905,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl // Paper end - rewrite chunk system public List getPushableEntities(Entity entity, AABB boundingBox) { diff --git a/leaf-server/minecraft-patches/features/0268-Bump-netty-to-4.2.x.patch b/leaf-server/minecraft-patches/features/0268-Bump-netty-to-4.2.x.patch index 705d8a5c..85e8cf97 100644 --- a/leaf-server/minecraft-patches/features/0268-Bump-netty-to-4.2.x.patch +++ b/leaf-server/minecraft-patches/features/0268-Bump-netty-to-4.2.x.patch @@ -28,10 +28,10 @@ index 340d5487fa778277b9560250271c5143d80d9987..99543330da85ddecd91c954c8aa386c8 public boolean isCommandBlockEnabled() { return true; diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index f89abb17dd84a58791068bbbf8fc64f4def644bf..26a2d2956b1643214421f76b9a4e96b7b1907c01 100644 +index 7306a3273adf5bfa8ebdec42b7aedf3656fb5850..540a1af9f4e0cf69ff987105a34d92a35e669fb0 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -2064,6 +2064,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { + serverLevel.getChunkSource().fullChunksNonSync.setThread(); // Leaf - thread unsafe chunk map + serverLevel.moonrise$getChunkTaskScheduler().chunkHolderManager.chunkHoldersNoSync.setThread(); // Leaf - thread unsafe chunk map ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread currentThread = (ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread) Thread.currentThread(); - currentThread.currentlyTickingServerLevel = serverLevel; + currentThread.currentTickingServerLevel = serverLevel; -@@ -1788,7 +1790,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop, AutoCl +@@ -1065,6 +1065,18 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl // Paper end - Perf: make sure loaded chunks get the inlined variant of this function } diff --git a/leaf-server/minecraft-patches/features/0277-remove-shouldTickBlocksAt-check.patch b/leaf-server/minecraft-patches/features/0277-remove-shouldTickBlocksAt-check.patch index c2975c0d..4e93ba33 100644 --- a/leaf-server/minecraft-patches/features/0277-remove-shouldTickBlocksAt-check.patch +++ b/leaf-server/minecraft-patches/features/0277-remove-shouldTickBlocksAt-check.patch @@ -5,7 +5,7 @@ Subject: [PATCH] remove shouldTickBlocksAt check diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 7333225a3d50bd14da38019541cc9daa22bbf9ed..66e10c357ed7b80083a0159c5b8262e5d214459e 100644 +index f32c1b7e77703265c675683ff7f774dd3d725957..94c5ba01caf50416dbc7512e825fcf70b7baac03 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -1540,7 +1540,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl @@ -16,4 +16,4 @@ index 7333225a3d50bd14da38019541cc9daa22bbf9ed..66e10c357ed7b80083a0159c5b8262e5 + } else if (runsNormally /*&& this.shouldTickBlocksAt(tickingBlockEntity.getPos())*/) { // Leaf - remove shouldTickBlocksAt check - duplicate at BoundTickingBlockEntity#tick tickingBlockEntity.tick(); // Paper start - rewrite chunk system - // Leaf start - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) - do not bother with condition work / make configurable + // Leaf start - SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) diff --git a/leaf-server/minecraft-patches/features/0283-optimize-onClimbable.patch b/leaf-server/minecraft-patches/features/0283-optimize-onClimbable.patch index 81e3b528..f6a8c56f 100644 --- a/leaf-server/minecraft-patches/features/0283-optimize-onClimbable.patch +++ b/leaf-server/minecraft-patches/features/0283-optimize-onClimbable.patch @@ -5,10 +5,10 @@ Subject: [PATCH] optimize onClimbable diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index b4a7281de8e016d059b417dee9aaedc02253a97f..5fbf5e01872834ce4a22fb8bfe25cc1dc4f8cf60 100644 +index ea137958c70f80349041cd27d1503f7b8def2a69..e8cd433ca5792b87a1763ccb14777115f9545cbd 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java -@@ -4991,7 +4991,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -5007,7 +5007,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess // Gale start - don't load chunks to activate climbing entities public @Nullable BlockState getInBlockStateIfLoaded() { if (this.inBlockState == null) { diff --git a/leaf-server/minecraft-patches/features/0287-cache-eye-block-position.patch b/leaf-server/minecraft-patches/features/0287-cache-eye-block-position.patch index df4bdb34..a3722120 100644 --- a/leaf-server/minecraft-patches/features/0287-cache-eye-block-position.patch +++ b/leaf-server/minecraft-patches/features/0287-cache-eye-block-position.patch @@ -5,7 +5,7 @@ Subject: [PATCH] cache eye block position diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 0b7bb37a29c3a5534db6f2bd781f7ef3bcc378cd..fb31d17226619a87dd88017dc0b6915b8d1ceecd 100644 +index 841d63333f55701357ed4dfd0d1be3faa5eeb3b1..ecfd852e64ffdb9e5eaebe7b1eeef4e99dda3b15 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -553,18 +553,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @@ -96,7 +96,7 @@ index 0b7bb37a29c3a5534db6f2bd781f7ef3bcc378cd..fb31d17226619a87dd88017dc0b6915b public void absSnapTo(double x, double y, double z, float yRot, float xRot) { this.absSnapTo(x, y, z); -@@ -4435,6 +4439,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4451,6 +4455,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess EntityDimensions dimensions = this.getDimensions(pose); this.dimensions = dimensions; this.eyeHeight = dimensions.eyeHeight(); @@ -104,7 +104,7 @@ index 0b7bb37a29c3a5534db6f2bd781f7ef3bcc378cd..fb31d17226619a87dd88017dc0b6915b } public void refreshDimensions() { -@@ -4443,6 +4448,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4459,6 +4464,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess EntityDimensions dimensions = this.getDimensions(pose); this.dimensions = dimensions; this.eyeHeight = dimensions.eyeHeight(); diff --git a/leaf-server/minecraft-patches/features/0288-optimize-entity-in-fluid.patch b/leaf-server/minecraft-patches/features/0288-optimize-entity-in-fluid.patch index 4e59f496..aa9c9d4c 100644 --- a/leaf-server/minecraft-patches/features/0288-optimize-entity-in-fluid.patch +++ b/leaf-server/minecraft-patches/features/0288-optimize-entity-in-fluid.patch @@ -5,10 +5,10 @@ Subject: [PATCH] optimize entity in fluid diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index fb31d17226619a87dd88017dc0b6915b8d1ceecd..cab664f5ae052002ae7258b33495b74164ea0b09 100644 +index ecfd852e64ffdb9e5eaebe7b1eeef4e99dda3b15..4689764a580d7dca468cdfa2b0fef7e70c57687f 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java -@@ -4844,12 +4844,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4860,12 +4860,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess final int minChunkZ = minBlockZ >> 4; final int maxChunkZ = maxBlockZ >> 4; @@ -23,7 +23,7 @@ index fb31d17226619a87dd88017dc0b6915b8d1ceecd..cab664f5ae052002ae7258b33495b741 if (chunk == null) continue; final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections(); // Leaf end - Prevent double chunk retrieving in entity fluid pushing check and fluid height updating -@@ -4861,7 +4861,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4877,7 +4877,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess continue; } final net.minecraft.world.level.chunk.LevelChunkSection section = sections[sectionIdx]; @@ -32,7 +32,7 @@ index fb31d17226619a87dd88017dc0b6915b8d1ceecd..cab664f5ae052002ae7258b33495b741 // empty continue; } -@@ -4918,7 +4918,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess +@@ -4934,7 +4934,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess this.fluidHeight.put(fluid, maxHeightDiff); diff --git a/leaf-server/minecraft-patches/features/0292-cache-collision-list.patch b/leaf-server/minecraft-patches/features/0292-cache-collision-list.patch index 9bc6972e..24c10063 100644 --- a/leaf-server/minecraft-patches/features/0292-cache-collision-list.patch +++ b/leaf-server/minecraft-patches/features/0292-cache-collision-list.patch @@ -5,10 +5,10 @@ Subject: [PATCH] cache collision list diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index aa919f4aeb79e00afd455cc73de2825c63a6afc7..6ee710e78b6c169ea4bb0c92787bb2631e4f68cf 100644 +index 5abd7fabcd1b860d66ce5363373c236393d1a580..fb9623d89376fc42fd5892e9c395439f55fa5ac5 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -1095,6 +1095,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1105,6 +1105,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe public final org.dreeam.leaf.world.DespawnMap despawnMap = new org.dreeam.leaf.world.DespawnMap(paperConfig()); // Leaf - optimize despawn public final org.dreeam.leaf.world.NatureSpawnChunkMap natureSpawnChunkMap = new org.dreeam.leaf.world.NatureSpawnChunkMap(); // Leaf - optimize mob spawning public final org.dreeam.leaf.world.RandomTickSystem randomTickSystem = new org.dreeam.leaf.world.RandomTickSystem(); // Leaf - optimize random tick @@ -18,7 +18,7 @@ index aa919f4aeb79e00afd455cc73de2825c63a6afc7..6ee710e78b6c169ea4bb0c92787bb263 final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = this.simpleRandom; // Paper - optimise random ticking // Leaf - Faster random generator - upcasting ChunkPos pos = chunk.getPos(); diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index e4aac8b9b178c2780e9ba0e1232927fdd63f0f22..2e1804ea6ee00585edfbd0d531cfde013f766b4c 100644 +index 30813f3d9aeece3d8a63300b8dc73ebaed8e75cd..f6d619709d4e5b0e6d1b943226579d7388835cdb 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -1552,8 +1552,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess diff --git a/leaf-server/minecraft-patches/features/0293-fast-bit-radix-sort.patch b/leaf-server/minecraft-patches/features/0293-fast-bit-radix-sort.patch index 9e8abd11..a09931fa 100644 --- a/leaf-server/minecraft-patches/features/0293-fast-bit-radix-sort.patch +++ b/leaf-server/minecraft-patches/features/0293-fast-bit-radix-sort.patch @@ -6,10 +6,10 @@ Subject: [PATCH] fast bit radix sort Co-authored-by: Taiyou06 diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 6ee710e78b6c169ea4bb0c92787bb2631e4f68cf..19a040133023c5fa7898c4e941b06948072accce 100644 +index fb9623d89376fc42fd5892e9c395439f55fa5ac5..739c16ecda34b1ee8cf59806cb13c80115377f1c 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -1096,6 +1096,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1106,6 +1106,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe public final org.dreeam.leaf.world.NatureSpawnChunkMap natureSpawnChunkMap = new org.dreeam.leaf.world.NatureSpawnChunkMap(); // Leaf - optimize mob spawning public final org.dreeam.leaf.world.RandomTickSystem randomTickSystem = new org.dreeam.leaf.world.RandomTickSystem(); // Leaf - optimize random tick public final org.dreeam.leaf.world.EntityCollisionCache entityCollisionCache = new org.dreeam.leaf.world.EntityCollisionCache(); // Leaf - cache collision list diff --git a/leaf-server/minecraft-patches/features/0295-Pluto-Expose-Direction-Plane-s-faces.patch b/leaf-server/minecraft-patches/features/0295-Pluto-Expose-Direction-Plane-s-faces.patch index fd3dcbf2..b76e3bbf 100644 --- a/leaf-server/minecraft-patches/features/0295-Pluto-Expose-Direction-Plane-s-faces.patch +++ b/leaf-server/minecraft-patches/features/0295-Pluto-Expose-Direction-Plane-s-faces.patch @@ -35,10 +35,10 @@ index 45093451fb25ae3bb1e57d2e53c49a1ae666e31e..c228135032b5f74fd1ec96a2cf52aa3e } } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 19a040133023c5fa7898c4e941b06948072accce..96e476ff9133627c9253def2c5a2ff376ac7b131 100644 +index ecef9a9215c9e843f62f6c9a7abb870fc7a5c789..f270d105d690f1b14473f24681b7694ab69d2495 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -1179,7 +1179,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1189,7 +1189,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // We only need to check blocks that are taller than the minimum step height if (org.purpurmc.purpur.PurpurConfig.smoothSnowAccumulationStep > 0 && layersValue >= org.purpurmc.purpur.PurpurConfig.smoothSnowAccumulationStep) { int layersValueMin = layersValue - org.purpurmc.purpur.PurpurConfig.smoothSnowAccumulationStep; @@ -100,10 +100,10 @@ index d26867def6c549c8c909213d4f91895f1168cae6..44e7b63e91d298c6c014ca45f356f82f } } diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 7e380c80fbfeaf670b49e8e923a84ec87a94344e..ef1e65e9652ba7ed4e48075cb7d440b3e880eb06 100644 +index e4d83ed875a159150e64e4745a03f61f6a909680..b2cb848ed90531bef6372b04228f539705ac36e6 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java -@@ -2066,7 +2066,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl +@@ -2063,7 +2063,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl public abstract Scoreboard getScoreboard(); public void updateNeighbourForOutputSignal(BlockPos pos, Block block) { @@ -196,10 +196,10 @@ index 3b69f9502c30a69519b7b722aecb9e25da8c55f0..b2c6af1dc9fa8330ee7d525b588725ac i++; } diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java -index a4e26381dcc831361c2d6bef6427d1d6eb7cef18..0b3d8fdd053ad74f32c33cb17e65aeeedd24d520 100644 +index 9adb432431df8bd25aece31678b6f1b727250041..ff5ee04f412c3c787200707e524d3135984def97 100644 --- a/net/minecraft/world/level/block/RedStoneWireBlock.java +++ b/net/minecraft/world/level/block/RedStoneWireBlock.java -@@ -160,7 +160,7 @@ public class RedStoneWireBlock extends Block { +@@ -158,7 +158,7 @@ public class RedStoneWireBlock extends Block { private BlockState getMissingConnections(BlockGetter level, BlockState state, BlockPos pos) { boolean flag = !level.getBlockState(pos.above()).isRedstoneConductor(level, pos); @@ -208,7 +208,7 @@ index a4e26381dcc831361c2d6bef6427d1d6eb7cef18..0b3d8fdd053ad74f32c33cb17e65aeee if (!state.getValue(PROPERTY_BY_DIRECTION.get(direction)).isConnected()) { RedstoneSide connectingSide = this.getConnectingSide(level, pos, direction, flag); state = state.setValue(PROPERTY_BY_DIRECTION.get(direction), connectingSide); -@@ -213,7 +213,7 @@ public class RedStoneWireBlock extends Block { +@@ -211,7 +211,7 @@ public class RedStoneWireBlock extends Block { protected void updateIndirectNeighbourShapes(BlockState state, LevelAccessor level, BlockPos pos, int flags, int recursionLeft) { BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); @@ -240,7 +240,7 @@ index a4e26381dcc831361c2d6bef6427d1d6eb7cef18..0b3d8fdd053ad74f32c33cb17e65aeee BlockPos blockPos = pos.relative(direction); if (level.getBlockState(blockPos).isRedstoneConductor(level, blockPos)) { this.checkCornerChangeAt(level, blockPos.above()); -@@ -521,7 +521,7 @@ public class RedStoneWireBlock extends Block { +@@ -512,7 +512,7 @@ public class RedStoneWireBlock extends Block { public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) { int powerValue = state.getValue(POWER); if (powerValue != 0) { @@ -249,7 +249,7 @@ index a4e26381dcc831361c2d6bef6427d1d6eb7cef18..0b3d8fdd053ad74f32c33cb17e65aeee RedstoneSide redstoneSide = state.getValue(PROPERTY_BY_DIRECTION.get(direction)); switch (redstoneSide) { case UP: -@@ -600,7 +600,7 @@ public class RedStoneWireBlock extends Block { +@@ -591,7 +591,7 @@ public class RedStoneWireBlock extends Block { private void updatesOnShapeChange(Level level, BlockPos pos, BlockState oldState, BlockState newState) { Orientation orientation = ExperimentalRedstoneUtils.initialOrientation(level, null, Direction.UP); diff --git a/leaf-server/paper-patches/features/0046-SparklyPaper-Parallel-world-ticking.patch b/leaf-server/paper-patches/features/0046-SparklyPaper-Parallel-world-ticking.patch index 9e0159c7..3b686391 100644 --- a/leaf-server/paper-patches/features/0046-SparklyPaper-Parallel-world-ticking.patch +++ b/leaf-server/paper-patches/features/0046-SparklyPaper-Parallel-world-ticking.patch @@ -1,21 +1,25 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Altiami -Date: Wed, 5 Mar 2025 13:16:44 -0800 +From: MrPowerGamerBR +Date: Sun, 25 May 2025 21:39:32 -0300 Subject: [PATCH] SparklyPaper: Parallel world ticking Original project: https://github.com/SparklyPower/SparklyPaper +Co-authored-by: Altiami +Co-authored-by: Taiyou06 Co-authored-by: MrlingXD <90316914+wling-art@users.noreply.github.com> +Commit: e2e154fb764650c90bbd23c9cede2b11ba1a08e2 + diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java -index 69cdd304d255d52c9b7dc9b6a33ffdb630b79abe..09dfab1ace05ee62df5cf6e292f8be0146e85a36 100644 +index 69cdd304d255d52c9b7dc9b6a33ffdb630b79abe..9ad7e85d3fc58725a99e0cc8a487d047b7a493db 100644 --- a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java +++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java @@ -14,6 +14,7 @@ import java.util.concurrent.atomic.AtomicInteger; public class TickThread extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(TickThread.class); -+ private static final boolean HARD_THROW = !org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.disableHardThrow; // SparklyPaper - parallel world ticking - THIS SHOULD NOT BE DISABLED SINCE IT CAN CAUSE DATA CORRUPTION!!! Anyhow, for production servers, if you want to make a test run to see if the server could crash, you can test it with this disabled ++ private static final boolean HARD_THROW = !org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled || !org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.disableHardThrow; // Leaf - SparklyPaper - parallel world ticking - THIS SHOULD NOT BE DISABLED SINCE IT CAN CAUSE DATA CORRUPTION!!! Anyhow, for production servers, if you want to make a test run to see if the server could crash, you can test it with this disabled private static String getThreadContext() { return "thread=" + Thread.currentThread().getName(); @@ -23,120 +27,139 @@ index 69cdd304d255d52c9b7dc9b6a33ffdb630b79abe..09dfab1ace05ee62df5cf6e292f8be01 public static void ensureTickThread(final String reason) { if (!isTickThread()) { LOGGER.error("Thread failed main thread check: " + reason + ", context=" + getThreadContext(), new Throwable()); -+ if (HARD_THROW) // SparklyPaper - parallel world ticking ++ if (HARD_THROW) // Leaf - SparklyPaper - parallel world ticking throw new IllegalStateException(reason); } } -@@ -33,8 +35,9 @@ public class TickThread extends Thread { - public static void ensureTickThread(final Level world, final BlockPos pos, final String reason) { +@@ -34,8 +36,11 @@ public class TickThread extends Thread { if (!isTickThreadFor(world, pos)) { final String ex = "Thread failed main thread check: " + -- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos; -+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + " - " + getTickThreadInformation(world.getServer()); // SparklyPaper - parallel world ticking - LOGGER.error(ex, new Throwable()); -+ if (HARD_THROW) // SparklyPaper - parallel world ticking - throw new IllegalStateException(ex); - } - } -@@ -42,8 +45,9 @@ public class TickThread extends Thread { - public static void ensureTickThread(final Level world, final BlockPos pos, final int blockRadius, final String reason) { - if (!isTickThreadFor(world, pos, blockRadius)) { - final String ex = "Thread failed main thread check: " + -- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + ", block_radius=" + blockRadius; -+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + ", block_radius=" + blockRadius + " - " + getTickThreadInformation(world.getServer()); // SparklyPaper - parallel world ticking - LOGGER.error(ex, new Throwable()); -+ if (HARD_THROW) // SparklyPaper - parallel world ticking - throw new IllegalStateException(ex); - } - } -@@ -51,8 +55,9 @@ public class TickThread extends Thread { - public static void ensureTickThread(final Level world, final ChunkPos pos, final String reason) { - if (!isTickThreadFor(world, pos)) { - final String ex = "Thread failed main thread check: " + -- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + pos; -+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + pos + " - " + getTickThreadInformation(world.getServer()); // SparklyPaper - parallel world ticking - LOGGER.error(ex, new Throwable()); -+ if (HARD_THROW) // SparklyPaper - parallel world ticking - throw new IllegalStateException(ex); - } - } -@@ -60,8 +65,9 @@ public class TickThread extends Thread { - public static void ensureTickThread(final Level world, final int chunkX, final int chunkZ, final String reason) { - if (!isTickThreadFor(world, chunkX, chunkZ)) { - final String ex = "Thread failed main thread check: " + -- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + new ChunkPos(chunkX, chunkZ); -+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + new ChunkPos(chunkX, chunkZ) + " - " + getTickThreadInformation(world.getServer()); // SparklyPaper - parallel world ticking - LOGGER.error(ex, new Throwable()); -+ if (HARD_THROW) // SparklyPaper - parallel world ticking - throw new IllegalStateException(ex); - } - } -@@ -69,8 +75,9 @@ public class TickThread extends Thread { - public static void ensureTickThread(final Entity entity, final String reason) { - if (!isTickThreadFor(entity)) { - final String ex = "Thread failed main thread check: " + -- reason + ", context=" + getThreadContext() + ", entity=" + EntityUtil.dumpEntity(entity); -+ reason + ", context=" + getThreadContext() + ", entity=" + EntityUtil.dumpEntity(entity) + " - " + getTickThreadInformation(entity.getServer()); // SparklyPaper - parallel world ticking - LOGGER.error(ex, new Throwable()); -+ if (HARD_THROW) // SparklyPaper - parallel world ticking - throw new IllegalStateException(ex); - } - } -@@ -78,8 +85,9 @@ public class TickThread extends Thread { - public static void ensureTickThread(final Level world, final AABB aabb, final String reason) { - if (!isTickThreadFor(world, aabb)) { - final String ex = "Thread failed main thread check: " + -- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb; -+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb + " - " + getTickThreadInformation(world.getServer()); // SparklyPaper - parallel world ticking - LOGGER.error(ex, new Throwable()); -+ if (HARD_THROW) // SparklyPaper - parallel world ticking - throw new IllegalStateException(ex); - } - } -@@ -87,12 +95,71 @@ public class TickThread extends Thread { - public static void ensureTickThread(final Level world, final double blockX, final double blockZ, final String reason) { - if (!isTickThreadFor(world, blockX, blockZ)) { - final String ex = "Thread failed main thread check: " + -- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + new Vec3(blockX, 0.0, blockZ); -+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + new Vec3(blockX, 0.0, blockZ) + " - " + getTickThreadInformation(world.getServer()); // SparklyPaper - parallel world ticking - LOGGER.error(ex, new Throwable()); -+ if (HARD_THROW) // SparklyPaper - parallel world ticking - throw new IllegalStateException(ex); + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos; +- LOGGER.error(ex, new Throwable()); +- throw new IllegalStateException(ex); ++ // Leaf start - SparklyPaper - parallel world ticking ++ final String threadInfo = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? ex + " - " + getTickThreadInformation(world.getServer()) : ex; ++ LOGGER.error(threadInfo, new Throwable()); ++ if (HARD_THROW) throw new IllegalStateException(threadInfo); ++ // Leaf end - SparklyPaper - parallel world ticking } } -+ // SparklyPaper start - parallel world ticking +@@ -43,8 +48,11 @@ public class TickThread extends Thread { + if (!isTickThreadFor(world, pos, blockRadius)) { + final String ex = "Thread failed main thread check: " + + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + ", block_radius=" + blockRadius; +- LOGGER.error(ex, new Throwable()); +- throw new IllegalStateException(ex); ++ // Leaf start - SparklyPaper - parallel world ticking ++ final String threadInfo = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? ex + " - " + getTickThreadInformation(world.getServer()) : ex; ++ LOGGER.error(threadInfo, new Throwable()); ++ if (HARD_THROW) throw new IllegalStateException(threadInfo); ++ // Leaf end - SparklyPaper - parallel world ticking + } + } + +@@ -52,8 +60,11 @@ public class TickThread extends Thread { + if (!isTickThreadFor(world, pos)) { + final String ex = "Thread failed main thread check: " + + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + pos; +- LOGGER.error(ex, new Throwable()); +- throw new IllegalStateException(ex); ++ // Leaf start - SparklyPaper - parallel world ticking ++ final String threadInfo = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? ex + " - " + getTickThreadInformation(world.getServer()) : ex; ++ LOGGER.error(threadInfo, new Throwable()); ++ if (HARD_THROW) throw new IllegalStateException(threadInfo); ++ // Leaf end - SparklyPaper - parallel world ticking + } + } + +@@ -61,8 +72,11 @@ public class TickThread extends Thread { + if (!isTickThreadFor(world, chunkX, chunkZ)) { + final String ex = "Thread failed main thread check: " + + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + new ChunkPos(chunkX, chunkZ); +- LOGGER.error(ex, new Throwable()); +- throw new IllegalStateException(ex); ++ // Leaf start - SparklyPaper - parallel world ticking ++ final String threadInfo = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? ex + " - " + getTickThreadInformation(world.getServer()) : ex; ++ LOGGER.error(threadInfo, new Throwable()); ++ if (HARD_THROW) throw new IllegalStateException(threadInfo); ++ // Leaf end - SparklyPaper - parallel world ticking + } + } + +@@ -70,8 +84,11 @@ public class TickThread extends Thread { + if (!isTickThreadFor(entity)) { + final String ex = "Thread failed main thread check: " + + reason + ", context=" + getThreadContext() + ", entity=" + EntityUtil.dumpEntity(entity); +- LOGGER.error(ex, new Throwable()); +- throw new IllegalStateException(ex); ++ // Leaf start - SparklyPaper - parallel world ticking ++ final String threadInfo = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? ex + " - " + getTickThreadInformation(entity.getServer()) : ex; ++ LOGGER.error(threadInfo, new Throwable()); ++ if (HARD_THROW) throw new IllegalStateException(threadInfo); ++ // Leaf end - SparklyPaper - parallel world ticking + } + } + +@@ -79,8 +96,11 @@ public class TickThread extends Thread { + if (!isTickThreadFor(world, aabb)) { + final String ex = "Thread failed main thread check: " + + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb; +- LOGGER.error(ex, new Throwable()); +- throw new IllegalStateException(ex); ++ // Leaf start - SparklyPaper - parallel world ticking ++ final String threadInfo = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? ex + " - " + getTickThreadInformation(world.getServer()) : ex; ++ LOGGER.error(threadInfo, new Throwable()); ++ if (HARD_THROW) throw new IllegalStateException(threadInfo); ++ // Leaf end - SparklyPaper - parallel world ticking + } + } + +@@ -88,11 +108,67 @@ public class TickThread extends Thread { + if (!isTickThreadFor(world, blockX, blockZ)) { + final String ex = "Thread failed main thread check: " + + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + new Vec3(blockX, 0.0, blockZ); +- LOGGER.error(ex, new Throwable()); +- throw new IllegalStateException(ex); ++ // Leaf start - SparklyPaper - parallel world ticking ++ final String threadInfo = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? ex + " - " + getTickThreadInformation(world.getServer()) : ex; ++ LOGGER.error(threadInfo, new Throwable()); ++ if (HARD_THROW) throw new IllegalStateException(threadInfo); ++ // Leaf end - SparklyPaper - parallel world ticking + } + } + ++ // Leaf start - SparklyPaper - parallel world ticking ++ // Copied from `TickThread#ensureTickThread(final String reason)` + // This is an additional method to check if the tick thread is bound to a specific world because, by default, Paper's isTickThread methods do not provide this information + // Because we only tick worlds in parallel (instead of regions), we can use this for our checks + public static void ensureTickThread(final net.minecraft.server.level.ServerLevel world, final String reason) { + if (!isTickThreadFor(world)) { -+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " - " + getTickThreadInformation(world.getServer()), new Throwable()); // SparklyPaper - parallel world ticking -+ if (HARD_THROW) throw new IllegalStateException(reason); ++ final String threadInfo = "Thread failed main thread check: " + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + " - " + getTickThreadInformation(world.getServer()); ++ LOGGER.error(threadInfo, new Throwable()); ++ if (HARD_THROW) throw new IllegalStateException(threadInfo); + } + } + + // This is an additional method to check if it is a tick thread but ONLY a tick thread + public static void ensureOnlyTickThread(final String reason) { -+ boolean isTickThread = isTickThread(); -+ boolean isServerLevelTickThread = isServerLevelTickThread(); -+ if (!isTickThread || isServerLevelTickThread) { -+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread ONLY tick thread check: " + reason, new Throwable()); -+ if (HARD_THROW) throw new IllegalStateException(reason); ++ if (!isTickThread() || isServerLevelTickThread()) { ++ final String threadInfo = "Thread failed tick-thread-only check: " + reason + ", context=" + getThreadContext(); ++ LOGGER.error(threadInfo, new Throwable()); ++ if (HARD_THROW) throw new IllegalStateException(threadInfo); + } + } + + // This is an additional method to check if the tick thread is bound to a specific world or if it is an async thread. + public static void ensureTickThreadOrAsyncThread(final net.minecraft.server.level.ServerLevel world, final String reason) { -+ boolean isValidTickThread = isTickThreadFor(world); -+ boolean isAsyncThread = !isTickThread(); -+ boolean isValid = isAsyncThread || isValidTickThread; -+ if (!isValid) { -+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread or async thread check: " + reason + " @ world " + world.getWorld().getName() + " - " + getTickThreadInformation(world.getServer()), new Throwable()); -+ if (HARD_THROW) throw new IllegalStateException(reason); ++ if (isTickThread() && !isTickThreadFor(world)) { ++ final String threadInfo = "Thread failed main thread or async thread check: " + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + " - " + getTickThreadInformation(world.getServer()); ++ LOGGER.error(threadInfo, new Throwable()); ++ if (HARD_THROW) throw new IllegalStateException(threadInfo); + } + } + -+ public static String getTickThreadInformation(net.minecraft.server.MinecraftServer minecraftServer) { ++ private static String getTickThreadInformation(net.minecraft.server.MinecraftServer minecraftServer) { + StringBuilder sb = new StringBuilder(); + Thread currentThread = Thread.currentThread(); + sb.append("Is tick thread? "); @@ -145,11 +168,7 @@ index 69cdd304d255d52c9b7dc9b6a33ffdb630b79abe..09dfab1ace05ee62df5cf6e292f8be01 + sb.append(currentThread instanceof ServerLevelTickThread); + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + sb.append("; Currently ticking level: "); -+ if (serverLevelTickThread.currentlyTickingServerLevel != null) { -+ sb.append(serverLevelTickThread.currentlyTickingServerLevel.getWorld().getName()); -+ } else { -+ sb.append("null"); -+ } ++ sb.append(serverLevelTickThread.currentTickingServerLevel != null ? serverLevelTickThread.currentTickingServerLevel.getWorld().getName() : "null"); + } + sb.append("; Is iterating over levels? "); + sb.append(minecraftServer.isIteratingOverLevels); @@ -161,85 +180,82 @@ index 69cdd304d255d52c9b7dc9b6a33ffdb630b79abe..09dfab1ace05ee62df5cf6e292f8be01 + public static boolean isServerLevelTickThread() { + return Thread.currentThread() instanceof ServerLevelTickThread; + } -+ // SparklyPaper end - parallel world ticking ++ // Leaf end - SparklyPaper - parallel world ticking + public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */ private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); -@@ -127,46 +194,74 @@ public class TickThread extends Thread { +@@ -127,46 +203,70 @@ public class TickThread extends Thread { } public static boolean isTickThreadFor(final Level world, final BlockPos pos) { - return isTickThread(); -+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking mod (use methods for what they were made for) ++ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for } public static boolean isTickThreadFor(final Level world, final BlockPos pos, final int blockRadius) { - return isTickThread(); -+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking mod (add missing replacement / use methods for what they were made for) ++ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for } public static boolean isTickThreadFor(final Level world, final ChunkPos pos) { - return isTickThread(); -+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking mod (use methods for what they were made for) ++ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for } public static boolean isTickThreadFor(final Level world, final Vec3 pos) { - return isTickThread(); -+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking mod (use methods for what they were made for) ++ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for } public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ) { - return isTickThread(); -+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking mod (use methods for what they were made for) ++ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for } public static boolean isTickThreadFor(final Level world, final AABB aabb) { - return isTickThread(); -+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking mod (use methods for what they were made for) ++ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for } public static boolean isTickThreadFor(final Level world, final double blockX, final double blockZ) { - return isTickThread(); -+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking mod (use methods for what they were made for) ++ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for } public static boolean isTickThreadFor(final Level world, final Vec3 position, final Vec3 deltaMovement, final int buffer) { - return isTickThread(); -+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking mod (use methods for what they were made for) ++ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for } public static boolean isTickThreadFor(final Level world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) { - return isTickThread(); -+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking mod (use methods for what they were made for) ++ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for } public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ, final int radius) { - return isTickThread(); -+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking mod (use methods for what they were made for) ++ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for + } + + public static boolean isTickThreadFor(final Entity entity) { ++ return isTickThreadFor(entity.level()); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for + } + -+ // SparklyPaper start - parallel world ticking ++ // Leaf start - SparklyPaper - parallel world ticking + // This is an additional method to check if the tick thread is bound to a specific world because, by default, Paper's isTickThread methods do not provide this information + // Because we only tick worlds in parallel (instead of regions), we can use this for our checks + public static boolean isTickThreadFor(final Level world) { + if (Thread.currentThread() instanceof ServerLevelTickThread serverLevelTickThread) { -+ return serverLevelTickThread.currentlyTickingServerLevel == world; -+ } else { -+ return isTickThread(); ++ return serverLevelTickThread.currentTickingServerLevel == world; + } + return isTickThread(); } - - public static boolean isTickThreadFor(final Entity entity) { -- return isTickThread(); -+ if (entity == null) { -+ return true; -+ } -+ -+ return isTickThreadFor(entity.level()); // Leaf - SparklyPaper - parallel world ticking mod (use methods for what they were made for) -+ } + + public static class ServerLevelTickThread extends TickThread { ++ ++ public net.minecraft.server.level.ServerLevel currentTickingServerLevel; ++ + public ServerLevelTickThread(String name) { + super(name); + } @@ -247,108 +263,102 @@ index 69cdd304d255d52c9b7dc9b6a33ffdb630b79abe..09dfab1ace05ee62df5cf6e292f8be01 + public ServerLevelTickThread(Runnable run, String name) { + super(run, name); + } -+ -+ public net.minecraft.server.level.ServerLevel currentlyTickingServerLevel; - } -+ // SparklyPaper end - parallel world ticking ++ } ++ // Leaf end - SparklyPaper - parallel world ticking } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 61121d2efd0df2fcafdc4c272e1cd1b986f42e24..ee5f342995a335593932a497c2bafd36d34cecb2 100644 +index 61121d2efd0df2fcafdc4c272e1cd1b986f42e24..0b63d9c6c71d5cdc791fca4705622b5d3546b8b5 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -480,7 +480,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -480,7 +480,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { } private boolean unloadChunk0(int x, int z, boolean save) { - org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot -+ // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot unload chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) -+ else ++ // Leaf start - SparklyPaper - parallel world ticking ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot unload chunk asynchronously"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ } else { + org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot -+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) ++ } ++ // Leaf end - SparklyPaper - parallel world ticking if (!this.isChunkLoaded(x, z)) { return true; } -@@ -497,6 +502,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -497,6 +503,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean refreshChunk(int x, int z) { -+ 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, z, "Cannot refresh chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot refresh chunk asynchronously"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs) ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); if (playerChunk == null) return false; -@@ -547,7 +554,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -547,7 +554,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean loadChunk(int x, int z, boolean generate) { - org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot -+ // Leaf start - SparklyPaper - parallel world ticking mod (make configurable) -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), x, z, "May not sync load chunks asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) -+ else ++ // Leaf start - SparklyPaper - parallel world ticking ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), x, z, "May not sync load chunks asynchronously"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ } else { + org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot -+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) ++ } ++ // Leaf end - SparklyPaper - parallel world ticking warnUnsafeChunk("loading a faraway chunk", x, z); // Paper ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper -@@ -775,6 +787,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -775,6 +788,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { -+ 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, loc.getX(), loc.getZ(), "Cannot generate tree asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, loc.getX(), loc.getZ(), "Cannot generate tree asynchronously"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs) this.world.captureTreeGeneration = true; this.world.captureBlockStates = true; boolean grownTree = this.generateTree(loc, type); -@@ -890,6 +904,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -890,6 +904,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { } public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source, Consumer configurator) { // Paper end - expand explosion API -+ 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, z, "Cannot create explosion asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot create explosion asynchronously"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs) net.minecraft.world.level.Level.ExplosionInteraction explosionType; if (!breakBlocks) { explosionType = net.minecraft.world.level.Level.ExplosionInteraction.NONE; // Don't break blocks -@@ -981,6 +997,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -981,6 +996,7 @@ 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) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x >> 4, z >> 4, "Cannot retrieve chunk asynchronously"); // Leaf - 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); -@@ -1011,6 +1029,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1011,6 +1027,7 @@ 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); -+ 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, pos, "Cannot retrieve chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, pos, "Cannot retrieve chunk asynchronously"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs) if (this.world.hasChunkAt(pos)) { net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkAt(pos); -@@ -2319,6 +2339,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -2319,6 +2336,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { -+ 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, position.getX(), position.getZ(), "Cannot send game event asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, position.getX(), position.getZ(), "Cannot send game event asynchronously"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs) getHandle().gameEvent(sourceEntity != null ? ((CraftEntity) sourceEntity).getHandle(): null, net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.get(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(gameEvent.getKey())).orElseThrow(), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position)); } // 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 dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad36c6b641 100644 +index dd122bbbe2c33183017dbde6997d3f1cd08479b5..72afe15ff95a4674f336168bbb0e14e5f6775106 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -74,12 +74,109 @@ public class CraftBlock implements Block { +@@ -74,12 +74,117 @@ public class CraftBlock implements Block { return new CraftBlock(world, position); } + // Leaf start - SparklyPaper - parallel world ticking + private ServerLevel getServerLevel() { -+ return (this.world instanceof ServerLevel serverLevel) ? serverLevel : null; ++ return this.world instanceof ServerLevel serverLevel ? serverLevel : null; + } + + private boolean needsBuffering(ServerLevel level, String handlingMode) { @@ -418,21 +428,25 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad 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 (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ 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(); ++ 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(); ++ } + } ++ } else { ++ return this.world.getBlockState(this.position); + } + // Leaf end - SparklyPaper - parallel world ticking } @@ -440,21 +454,25 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad 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"); ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "getNMSFluid"); + -+ try { ++ 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(); ++ } ++ } else { + 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 } public BlockPos getPosition() { -@@ -142,10 +239,12 @@ public class CraftBlock implements Block { +@@ -142,10 +247,12 @@ public class CraftBlock implements Block { return this.getWorld().getChunkAt(this); } @@ -467,15 +485,11 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad public void setData(final byte data, boolean applyPhysics) { if (applyPhysics) { this.setData(data, net.minecraft.world.level.block.Block.UPDATE_ALL); -@@ -155,12 +254,18 @@ public class CraftBlock implements Block { +@@ -155,12 +262,14 @@ public class CraftBlock implements Block { } private void setData(final byte data, int flags) { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); -+ } -+ // SparklyPaper end - parallel world ticking ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); // Leaf - SparklyPaper - parallel world ticking this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flags); } @@ -487,7 +501,7 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad return CraftMagicNumbers.toLegacyData(state); } -@@ -177,6 +282,7 @@ public class CraftBlock implements Block { +@@ -177,6 +286,7 @@ public class CraftBlock implements Block { @Override public void setType(Material type, boolean applyPhysics) { Preconditions.checkArgument(type != null, "Material cannot be null"); @@ -495,19 +509,15 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad this.setBlockData(type.createBlockData(), applyPhysics); } -@@ -196,6 +302,11 @@ public class CraftBlock implements Block { +@@ -196,6 +306,7 @@ public class CraftBlock implements Block { } public static boolean setBlockState(LevelAccessor world, BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState, boolean applyPhysics) { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, pos, "Cannot modify world asynchronously"); -+ } -+ // SparklyPaper end - parallel world ticking ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, pos, "Cannot modify world asynchronously"); // Leaf - SparklyPaper - parallel world ticking // SPIGOT-611: need to do this to prevent glitchiness. Easier to handle this here (like /setblock) than to fix weirdness in block entity cleanup if (oldState.hasBlockEntity() && newState.getBlock() != oldState.getBlock()) { // SPIGOT-3725 remove old block entity if block changes // SPIGOT-4612: faster - just clear tile -@@ -227,22 +338,62 @@ public class CraftBlock implements Block { +@@ -227,22 +338,74 @@ public class CraftBlock implements Block { @Override public Material getType() { @@ -519,20 +529,24 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad 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 ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ 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); ++ 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; + } -+ 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; ++ } else { ++ return (byte) this.world.getMinecraftWorld().getMaxLocalRawBrightness(this.position); + } + // Leaf end - SparklyPaper - parallel world ticking } @@ -541,16 +555,20 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad 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 ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ 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 ++ 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; ++ } ++ } else { + 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 } @@ -559,117 +577,54 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad 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 ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ 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 ++ 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; ++ } ++ } else { + 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) { -@@ -287,47 +438,32 @@ public class CraftBlock implements Block { - } - - 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; -+ }; -+ // 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 -@@ -344,18 +480,65 @@ public class CraftBlock implements Block { +@@ -344,18 +507,69 @@ public class CraftBlock implements Block { @Override public Biome getBiome() { - 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 (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ 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 ++ 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 ++ } + } ++ } else { ++ return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); + } + // Leaf end - SparklyPaper - parallel world ticking } @@ -679,25 +634,29 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad public Biome getComputedBiome() { - 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 (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ 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 ++ 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 ++ } + } ++ } else { ++ return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ()); + } + // Leaf end - SparklyPaper - parallel world ticking } @@ -705,34 +664,34 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad @Override public void setBiome(Biome bio) { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); -+ } -+ // SparklyPaper end - parallel world ticking ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); // Leaf - SparklyPaper - parallel world ticking this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio); } -@@ -371,12 +554,50 @@ public class CraftBlock implements Block { +@@ -371,12 +585,58 @@ 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 ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ 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; ++ 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; + } -+ 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; ++ } else { ++ return this.world.getMinecraftWorld().getDirectSignalTo(this.position) > 0; + } + // Leaf end - SparklyPaper - parallel world ticking } @@ -741,50 +700,58 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad public boolean isBlockIndirectlyPowered() { - 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 (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ 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); ++ 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; + } -+ 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; + } ++ } else { ++ return this.world.getMinecraftWorld().hasNeighborSignal(this.position); + } + // Leaf end - SparklyPaper - parallel world ticking } @Override -@@ -398,45 +619,104 @@ public class CraftBlock implements Block { +@@ -398,38 +658,40 @@ public class CraftBlock implements Block { @Override 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 ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ 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)); ++ 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; + } -+ 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; ++ } else { ++ return this.world.getMinecraftWorld().hasSignal(this.position, CraftBlock.blockFaceToNotch(face)); + } + // Leaf end - SparklyPaper - parallel world ticking } @@ -792,38 +759,16 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad @Override public boolean isBlockFaceIndirectlyPowered(BlockFace face) { - int power = this.world.getMinecraftWorld().getSignal(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, "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); - +- - Block relative = this.getRelative(face); - if (relative.getType() == Material.REDSTONE_WIRE) { - return Math.max(power, relative.getData()) > 0; // todo remove legacy usage -+ return false; - } - +- } +- - return power > 0; -+ // Leaf end - SparklyPaper - parallel world ticking ++ return pwt$isBlockFaceIndirectlyPowered(face); // Leaf - SparklyPaper - parallel world ticking } -+ // Leaf start - SparklyPaper - parallel world ticking @Override public int getBlockPower(BlockFace face) { - int power = 0; @@ -841,61 +786,15 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad - } - - private static int getPower(int power, net.minecraft.world.level.block.state.BlockState state) { -- if (!state.is(Blocks.REDSTONE_WIRE)) { -- return power; -+ 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 { -- return Math.max(state.getValue(RedStoneWireBlock.POWER), 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; -+ 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; -+ } - } - } - -+ // 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); -+ } ++ return pwt$getBlockPower(face); // Leaf - SparklyPaper - parallel world ticking + } -+ // Leaf end - SparklyPaper - parallel world ticking + - @Override - public int getBlockPower() { - return this.getBlockPower(BlockFace.SELF); -@@ -478,23 +758,35 @@ public class CraftBlock implements Block { ++ // Leaf - SparklyPaper - parallel world ticking - Static helper, safe ++ public static int getPower(int power, net.minecraft.world.level.block.state.BlockState state) { // Leaf - SparklyPaper - parallel world ticking - private -> public + if (!state.is(Blocks.REDSTONE_WIRE)) { + return power; + } else { +@@ -478,23 +740,37 @@ public class CraftBlock implements Block { @Override public PistonMoveReaction getPistonMoveReaction() { @@ -907,16 +806,18 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad public boolean breakNaturally() { - 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) { ++ ServerLevel level = getServerLevel(); + 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."); + } ++ // Delegates to overload ++ return this.breakNaturally(null, true, true); // Default to dropping XP and triggering effects ++ } else { ++ 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 } @@ -924,7 +825,7 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad 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 ++ return org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? this.breakNaturally(item, true, true) : this.breakNaturally(item, false); // Leaf - SparklyPaper - parallel world ticking - Delegates to full overload } @Override @@ -934,29 +835,16 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad } @Override -@@ -506,84 +798,147 @@ public class CraftBlock implements Block { +@@ -505,85 +781,12 @@ public class CraftBlock implements Block { + @Override public boolean breakNaturally(ItemStack item, boolean triggerEffect, boolean dropExperience, boolean forceEffect) { // 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 state = this.getNMS(); - net.minecraft.world.level.block.Block block = state.getBlock(); - net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item); +- // Order matters here, need to drop before setting to air so skulls can get their data +- net.minecraft.world.level.block.state.BlockState state = this.getNMS(); +- net.minecraft.world.level.block.Block block = state.getBlock(); +- net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item); - boolean result = false; -+ boolean droppedItems = false; - +- - // Modelled off Player#hasCorrectToolForDrops - if (block != Blocks.AIR && (item == null || !state.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(state))) { - net.minecraft.world.level.block.Block.dropResources(state, this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), null, nmsItem, false); // Paper - Properly handle xp dropping @@ -965,113 +853,44 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad - // Paper end - result = true; - } -+ // Experience dropping requires ServerLevel -+ ServerLevel serverLevelForDrops = getServerLevel(); // Re-get ServerLevel specifically for drop logic - +- - if ((result && triggerEffect) || (forceEffect && block != Blocks.AIR)) { - if (state.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(state)); -+ if (serverLevelForDrops != null) { // Only attempt drops/XP if we have a ServerLevel -+ // Check if block should drop items -+ if (!state.isAir() && (item == null || !state.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(state))) { -+ // Drop items using ServerLevel -+ net.minecraft.world.level.block.Block.dropResources(state, serverLevelForDrops, this.position, this.world.getBlockEntity(this.position), null, nmsItem, false); -+ -+ // Drop experience using ServerLevel -+ if (dropExperience) { -+ int xp = block.getExpDrop(state, 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 && !state.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 && !state.isAir()) { -+ int eventId = (state.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(state) : 0; -+ this.world.levelEvent(eventId, this.position, eventData); - } - } - +- } +- } +- - // SPIGOT-6778: Directly call setBlock instead of setBlockState, so that the block entity is not removed and custom remove logic is run. - // Paper start - improve breakNaturally -+ // Trigger effect using LevelAccessor (safe) -+ if ((droppedItems && triggerEffect) || (forceEffect && block != Blocks.AIR)) { -+ int eventId = (state.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(state) : 0; -+ this.world.levelEvent(eventId, this.position, eventData); -+ } -+ -+ // 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, state); - } +- boolean destroyed = this.world.removeBlock(this.position, false); +- if (destroyed) { +- block.destroy(this.world, this.position, state); +- } - 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) { +- 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) { +- } else if (block instanceof net.minecraft.world.level.block.TurtleEggBlock turtleEggBlock) { - turtleEggBlock.decreaseEggs(this.world.getMinecraftWorld(), this.position, state); -+ turtleEggBlock.decreaseEggs(nmsLevelForSpecialCases, this.position, state); - } -+ } 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 ++ return pwt$breakNaturally(item, triggerEffect, dropExperience, forceEffect); // Leaf - SparklyPaper - parallel world ticking - Write operation check } @Override public boolean applyBoneMeal(BlockFace face) { -+ // 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; -+ } -+ -+ // Original logic requires ServerLevel (level is guaranteed non-null here) - Direction direction = CraftBlock.blockFaceToNotch(face); - BlockFertilizeEvent event = null; +- Direction direction = CraftBlock.blockFaceToNotch(face); +- BlockFertilizeEvent event = null; - 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)); - - // SPIGOT-6895: Call StructureGrowEvent and BlockFertilizeEvent +- +- // SPIGOT-6895: Call StructureGrowEvent and BlockFertilizeEvent - world.captureTreeGeneration = true; -+ level.captureTreeGeneration = true; - InteractionResult result = BoneMealItem.applyBonemeal(context); +- InteractionResult result = BoneMealItem.applyBonemeal(context); - world.captureTreeGeneration = false; - - if (!world.capturedBlockStates.isEmpty()) { @@ -1079,91 +898,46 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad - SaplingBlock.treeType = null; - List states = new ArrayList<>(world.capturedBlockStates.values()); - world.capturedBlockStates.clear(); -+ level.captureTreeGeneration = false; -+ -+ if (!level.capturedBlockStates.isEmpty()) { -+ 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; - - if (treeType != null) { +- StructureGrowEvent structureEvent = null; +- +- if (treeType != null) { - structureEvent = new StructureGrowEvent(this.getLocation(), treeType, true, null, states); -+ structureEvent = new StructureGrowEvent(this.getLocation(), treeType, true, null, bukkitStates); - Bukkit.getPluginManager().callEvent(structureEvent); - } - +- Bukkit.getPluginManager().callEvent(structureEvent); +- } +- - event = new BlockFertilizeEvent(CraftBlock.at(world, this.getPosition()), null, states); -+ event = new BlockFertilizeEvent(this, null, bukkitStates); // Use 'this' as the CraftBlock - event.setCancelled(structureEvent != null && structureEvent.isCancelled()); - Bukkit.getPluginManager().callEvent(event); - - if (!event.isCancelled()) { +- event.setCancelled(structureEvent != null && structureEvent.isCancelled()); +- Bukkit.getPluginManager().callEvent(event); +- +- if (!event.isCancelled()) { - for (BlockState state : states) { -+ for (BlockState state : bukkitStates) { - CraftBlockState craftBlockState = (CraftBlockState) state; +- CraftBlockState craftBlockState = (CraftBlockState) state; - craftBlockState.place(craftBlockState.getFlags()); - world.checkCapturedTreeStateForObserverNotify(this.position, craftBlockState); // Paper - notify observers even if grow failed -+ craftBlockState.place(craftBlockState.getFlags()); // This performs the actual block changes -+ // Notify observers using the captured state info -+ level.checkCapturedTreeStateForObserverNotify(this.position, craftBlockState); - } - } - } - -+ // 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 +- } +- } +- } +- +- return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled()); ++ return pwt$applyBoneMeal(face); // Leaf - SparklyPaper - parallel world ticking - Write operation check } @Override -@@ -598,20 +953,45 @@ public class CraftBlock implements Block { +@@ -598,20 +801,12 @@ public class CraftBlock implements Block { @Override public Collection getDrops(ItemStack item, Entity entity) { - net.minecraft.world.level.block.state.BlockState state = 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 Player#hasCorrectToolForDrops - if (item == null || CraftBlockData.isPreferredTool(state, nms)) { - return net.minecraft.world.level.block.Block.getDrops(state, 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 state = 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 || !state.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(state)) { -+ // Call NMS getDrops using the verified ServerLevel -+ List nmsDrops = net.minecraft.world.level.block.Block.getDrops( -+ state, 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 +- return Collections.emptyList(); +- } ++ return pwt$getDrops(item, entity); // Leaf - SparklyPaper - parallel world ticking - Write operation check } @Override @@ -1172,23 +946,27 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad net.minecraft.world.level.block.state.BlockState state = this.getNMS(); net.minecraft.world.item.ItemStack nms = CraftItemStack.asNMSCopy(item); return CraftBlockData.isPreferredTool(state, nms); -@@ -620,9 +1000,23 @@ public class CraftBlock implements Block { +@@ -620,9 +815,27 @@ public class CraftBlock implements Block { @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 ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ // 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() ++ 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; ++ } ++ } else { + 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 } @@ -1197,90 +975,44 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad @Override public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { this.getCraftWorld().getBlockMetadata().setMetadata(this, metadataKey, newMetadataValue); -@@ -645,57 +1039,148 @@ public class CraftBlock implements Block { +@@ -645,7 +858,25 @@ 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 ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ // 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. ++ 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. ++ } ++ } else { ++ return this.getNMS().getCollisionShape(this.world, this.position).isEmpty(); + } + // Leaf end - SparklyPaper - parallel world ticking } @Override - public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { -+ // 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 in a different world"); - start.checkFinite(); +@@ -663,39 +894,68 @@ public class CraftBlock implements Block { + return null; + } - 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.toVec3(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.convertFromInternal(this.world, nmsHitResult); -+ } else { -+ // Strict/Disabled/Non-ServerLevel path -+ checkStrictMode(level, handlingMode, "rayTrace"); -+ try { -+ // Calculate start and end points for the ray trace -+ Vec3 startPos = CraftLocation.toVec3(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.toFluid(fluidCollisionMode), CollisionContext.empty()), -+ this.position // Provide the block's position here -+ ); - +- - HitResult hitResult = this.world.clip(new ClipContext(startPos, endPos, ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toFluid(fluidCollisionMode), CollisionContext.empty()), this.position); - return CraftRayTraceResult.convertFromInternal(this.world, hitResult); -+ // Convert NMS result to Bukkit result -+ return CraftRayTraceResult.convertFromInternal(this.world, 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 ++ return pwt$rayTrace(start, direction, maxDistance, fluidCollisionMode); // Leaf - SparklyPaper - parallel world ticking } @Override @@ -1289,34 +1021,11 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad - - 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 ++ return pwt$getBoundingBox(); // Leaf - SparklyPaper - parallel world ticking } @Override @@ -1324,18 +1033,23 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad - 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 ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ // 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() ++ 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 ++ } ++ } else { + 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 ++ return new CraftVoxelShape(shape); + } + // Leaf end - SparklyPaper - parallel world ticking } @@ -1346,76 +1060,70 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad - 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; ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ 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_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 ++ } ++ } ++ } else { ++ net.minecraft.world.level.block.state.BlockState iblockdata = ((CraftBlockData) data).getState(); ++ net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); - 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 -+ } ++ return iblockdata.canSurvive(world, this.position); + } + // Leaf end - SparklyPaper - parallel world ticking } @Override -@@ -711,7 +1196,10 @@ public class CraftBlock implements Block { +@@ -711,7 +971,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(); ++ net.minecraft.world.level.block.SoundType nmsSoundType = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? this.getNMS().getSoundType() : getNMS().getBlock().defaultBlockState().getSoundType(); + return new com.destroystokyo.paper.block.CraftBlockSoundGroup(nmsSoundType); + // Leaf end - SparklyPaper - parallel world ticking } @Override -@@ -724,26 +1212,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 - } +@@ -730,20 +993,511 @@ public class CraftBlock implements Block { @Override public void tick() { - final ServerLevel level = this.world.getMinecraftWorld(); + // Leaf start - SparklyPaper - parallel world ticking - Write operation check + // (block ticks can modify state) -+ ServerLevel level = getServerLevel(); ++ ServerLevel level; + if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ level = getServerLevel(); + 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; ++ } else { ++ level = this.world.getMinecraftWorld(); + } ++ // Leaf end - SparklyPaper - parallel world ticking - + this.getNMS().tick(level, this.position, level.random); -+ // Leaf end - SparklyPaper - parallel world ticking - } @@ -1424,17 +1132,17 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad - this.getNMSFluid().tick(this.world.getMinecraftWorld(), this.position, this.getNMS()); + // Leaf start - SparklyPaper - parallel world ticking + // Fluid ticks can modify state -+ ServerLevel level = getServerLevel(); ++ ServerLevel level; + if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ level = getServerLevel(); + 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; ++ } else { ++ level = this.world.getMinecraftWorld(); + } + this.getNMSFluid().tick(level, this.position, this.getNMS()); + // Leaf end - SparklyPaper - parallel world ticking @@ -1445,94 +1153,546 @@ index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d447b4180f8db81dd28a34cd60cec2ad - final ServerLevel level = this.world.getMinecraftWorld(); + // Leaf start - SparklyPaper - parallel world ticking - Write operation check + // (random ticks can modify state) -+ ServerLevel level = getServerLevel(); ++ ServerLevel level; + if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ level = getServerLevel(); + 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; ++ } else { ++ level = this.world.getMinecraftWorld(); + } - this.getNMS().randomTick(level, this.position, level.random); + // Leaf end - SparklyPaper - parallel world ticking - Write operation check + this.getNMS().randomTick(level, this.position, level.random); } // Paper end ++ ++ private boolean pwt$isBlockFaceIndirectlyPowered(BlockFace face) { ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ 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; ++ } ++ } else { ++ 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; // todo remove legacy usage ++ } ++ ++ return power > 0; ++ } ++ } ++ ++ private int pwt$getBlockPower(BlockFace face) { ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ 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 { ++ // 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; ++ 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; ++ } ++ } ++ } else { ++ 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 boolean pwt$breakNaturally(ItemStack item, boolean triggerEffect, boolean dropExperience, boolean forceEffect) { ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ // Order matters here, need to drop before setting to air so skulls can get their data ++ // (already done in simpler overload, but check again for safety) ++ ServerLevel level = getServerLevel(); ++ 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 state = this.getNMS(); ++ net.minecraft.world.level.block.Block block = state.getBlock(); ++ net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item); ++ 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 (!state.isAir() && (item == null || !state.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(state))) { ++ // Drop items using ServerLevel ++ net.minecraft.world.level.block.Block.dropResources(state, serverLevelForDrops, this.position, this.world.getBlockEntity(this.position), null, nmsItem, false); ++ ++ // Drop experience using ServerLevel ++ if (dropExperience) { ++ int xp = block.getExpDrop(state, 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 && !state.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 && !state.isAir()) { ++ int eventId = (state.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(state) : 0; ++ this.world.levelEvent(eventId, this.position, eventData); ++ } ++ } ++ ++ // Trigger effect using LevelAccessor (safe) ++ if ((droppedItems && triggerEffect) || (forceEffect && block != Blocks.AIR)) { ++ int eventId = (state.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(state) : 0; ++ this.world.levelEvent(eventId, this.position, eventData); ++ } ++ ++ // 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, state); ++ } ++ // 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(nmsLevelForSpecialCases, this.position, nmsItem); ++ } else if (block instanceof net.minecraft.world.level.block.TurtleEggBlock turtleEggBlock) { ++ turtleEggBlock.decreaseEggs(nmsLevelForSpecialCases, this.position, state); ++ } ++ } 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 true if the block was successfully destroyed AND items were dropped (or would have dropped if tool was right) ++ return destroyed && droppedItems; ++ } else { ++ // Order matters here, need to drop before setting to air so skulls can get their data ++ net.minecraft.world.level.block.state.BlockState state = this.getNMS(); ++ net.minecraft.world.level.block.Block block = state.getBlock(); ++ net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item); ++ boolean result = false; ++ ++ // Modelled off Player#hasCorrectToolForDrops ++ if (block != Blocks.AIR && (item == null || !state.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(state))) { ++ net.minecraft.world.level.block.Block.dropResources(state, this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), null, nmsItem, false); // Paper - Properly handle xp dropping ++ // Paper start - improve Block#breakNaturally ++ if (dropExperience) ++ block.popExperience(this.world.getMinecraftWorld(), this.position, block.getExpDrop(state, this.world.getMinecraftWorld(), this.position, nmsItem, true)); ++ // Paper end ++ result = true; ++ } ++ ++ if ((result && triggerEffect) || (forceEffect && block != Blocks.AIR)) { ++ if (state.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(state)); ++ } ++ } ++ ++ // SPIGOT-6778: Directly call setBlock instead of setBlockState, so that the block entity is not removed and custom remove logic is run. ++ // Paper start - improve breakNaturally ++ boolean destroyed = this.world.removeBlock(this.position, false); ++ if (destroyed) { ++ block.destroy(this.world, this.position, state); ++ } ++ if (result) { ++ // special cases ++ if (block instanceof net.minecraft.world.level.block.IceBlock iceBlock) { ++ iceBlock.afterDestroy(this.world.getMinecraftWorld(), this.position, nmsItem); ++ } else if (block instanceof net.minecraft.world.level.block.TurtleEggBlock turtleEggBlock) { ++ turtleEggBlock.decreaseEggs(this.world.getMinecraftWorld(), this.position, state); ++ } ++ } ++ return destroyed && result; ++ // Paper end ++ } ++ } ++ ++ private boolean pwt$applyBoneMeal(BlockFace face) { ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ ServerLevel level = getServerLevel(); ++ 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 ++ } ++ ++ // Original logic requires ServerLevel (level is guaranteed non-null here) ++ Direction direction = CraftBlock.blockFaceToNotch(face); ++ BlockFertilizeEvent event = null; ++ UseOnContext context = new UseOnContext(level, null, InteractionHand.MAIN_HAND, Items.BONE_MEAL.getDefaultInstance(), new BlockHitResult(Vec3.ZERO, direction, this.getPosition(), false)); ++ ++ // SPIGOT-6895: Call StructureGrowEvent and BlockFertilizeEvent ++ level.captureTreeGeneration = true; ++ InteractionResult result = BoneMealItem.applyBonemeal(context); ++ level.captureTreeGeneration = false; ++ ++ if (!level.capturedBlockStates.isEmpty()) { ++ 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; ++ ++ if (treeType != null) { ++ structureEvent = new StructureGrowEvent(this.getLocation(), treeType, true, null, bukkitStates); ++ Bukkit.getPluginManager().callEvent(structureEvent); ++ } ++ ++ 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 state : bukkitStates) { ++ CraftBlockState craftBlockState = (CraftBlockState) state; ++ craftBlockState.place(craftBlockState.getFlags()); // This performs the actual block changes ++ // Notify observers using the captured state info ++ level.checkCapturedTreeStateForObserverNotify(this.position, craftBlockState); ++ } ++ } ++ } ++ ++ // Return true only if bonemeal succeeded AND the event wasn't cancelled ++ return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled()); ++ } else { ++ Direction direction = CraftBlock.blockFaceToNotch(face); ++ BlockFertilizeEvent event = null; ++ 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)); ++ ++ // SPIGOT-6895: Call StructureGrowEvent and BlockFertilizeEvent ++ world.captureTreeGeneration = true; ++ InteractionResult result = BoneMealItem.applyBonemeal(context); ++ world.captureTreeGeneration = false; ++ ++ if (!world.capturedBlockStates.isEmpty()) { ++ TreeType treeType = SaplingBlock.treeType; ++ SaplingBlock.treeType = null; ++ List states = new ArrayList<>(world.capturedBlockStates.values()); ++ world.capturedBlockStates.clear(); ++ StructureGrowEvent structureEvent = null; ++ ++ if (treeType != null) { ++ structureEvent = new StructureGrowEvent(this.getLocation(), treeType, true, null, states); ++ Bukkit.getPluginManager().callEvent(structureEvent); ++ } ++ ++ event = new BlockFertilizeEvent(CraftBlock.at(world, this.getPosition()), null, states); ++ event.setCancelled(structureEvent != null && structureEvent.isCancelled()); ++ Bukkit.getPluginManager().callEvent(event); ++ ++ if (!event.isCancelled()) { ++ for (BlockState state : states) { ++ CraftBlockState craftBlockState = (CraftBlockState) state; ++ craftBlockState.place(craftBlockState.getFlags()); ++ world.checkCapturedTreeStateForObserverNotify(this.position, craftBlockState); // Paper - notify observers even if grow failed ++ } ++ } ++ } ++ ++ return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled()); ++ } ++ } ++ ++ private Collection pwt$getDrops(ItemStack item, Entity entity) { ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ // 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(); ++ } ++ ++ // 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 state = 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 || !state.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(state)) { ++ // Call NMS getDrops using the verified ServerLevel ++ List nmsDrops = net.minecraft.world.level.block.Block.getDrops( ++ state, 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(); ++ } ++ } else { ++ net.minecraft.world.level.block.state.BlockState state = this.getNMS(); ++ net.minecraft.world.item.ItemStack nms = CraftItemStack.asNMSCopy(item); ++ ++ // Modelled off Player#hasCorrectToolForDrops ++ if (item == null || CraftBlockData.isPreferredTool(state, nms)) { ++ return net.minecraft.world.level.block.Block.getDrops(state, 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 { ++ return Collections.emptyList(); ++ } ++ } ++ } ++ ++ private RayTraceResult pwt$rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ 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.convertFromInternal(this.world, nmsHitResult); ++ } else { ++ // Strict/Disabled/Non-ServerLevel path ++ checkStrictMode(level, handlingMode, "rayTrace"); ++ try { ++ // Calculate start and end points for the ray trace ++ Vec3 startPos = CraftLocation.toVec3(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.toFluid(fluidCollisionMode), CollisionContext.empty()), ++ this.position // Provide the block's position here ++ ); ++ ++ // Convert NMS result to Bukkit result ++ return CraftRayTraceResult.convertFromInternal(this.world, 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 ++ } ++ } ++ } else { ++ Vector dir = direction.clone().normalize().multiply(maxDistance); ++ Vec3 startPos = CraftLocation.toVec3(start); ++ Vec3 endPos = startPos.add(dir.getX(), dir.getY(), dir.getZ()); ++ ++ HitResult hitResult = this.world.clip(new ClipContext(startPos, endPos, ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toFluid(fluidCollisionMode), CollisionContext.empty()), this.position); ++ return CraftRayTraceResult.convertFromInternal(this.world, hitResult); ++ } ++ } ++ ++ private BoundingBox pwt$getBoundingBox() { ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ // 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); ++ } ++ } else { ++ 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 ++ } ++ ++ 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); ++ } ++ } } diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -index 5d4faad9df4824cfd61abfd4df011c006f114424..40fb6081bc2a6045c76f6e86584327758627f444 100644 +index 5d4faad9df4824cfd61abfd4df011c006f114424..55308342f93d843796aa6a7d331846302558d087 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -@@ -33,6 +33,27 @@ public abstract class CraftBlockEntityState extends Craft +@@ -33,6 +33,33 @@ public abstract class CraftBlockEntityState extends Craft private final T snapshot; public boolean snapshotDisabled; // Paper public static boolean DISABLE_SNAPSHOT = false; // Paper -+ public static ThreadLocal DISABLE_SNAPSHOT_TL = ThreadLocal.withInitial(() -> Boolean.FALSE); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (distinguish name) ++ private static final ThreadLocal DISABLE_SNAPSHOT_TL = ThreadLocal.withInitial(() -> Boolean.FALSE); // Leaf - SparklyPaper - parallel world ticking + -+ // Leaf start - SparklyPaper - parallel world ticking mod ++ // Leaf start - SparklyPaper - parallel world ticking + // refer to original field in case plugins attempt to modify it + public static boolean getDisableSnapshotTL() { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && DISABLE_SNAPSHOT_TL.get()) -+ return true; -+ synchronized (CraftBlockEntityState.class) { ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ if (DISABLE_SNAPSHOT_TL.get()) return true; ++ synchronized (CraftBlockEntityState.class) { ++ return DISABLE_SNAPSHOT; ++ } ++ } else { + return DISABLE_SNAPSHOT; + } + } + + // update original field in case plugins attempt to access it + public static void setDisableSnapshotTL(boolean value) { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { + DISABLE_SNAPSHOT_TL.set(value); -+ synchronized (CraftBlockEntityState.class) { -+ DISABLE_SNAPSHOT = value; ++ synchronized (CraftBlockEntityState.class) { ++ DISABLE_SNAPSHOT = value; ++ } ++ } else { ++ DISABLE_SNAPSHOT = value; + } + } -+ // Leaf end - SparklyPaper - parallel world ticking mod ++ // Leaf end - SparklyPaper - parallel world ticking public CraftBlockEntityState(World world, T blockEntity) { super(world, blockEntity.getBlockPos(), blockEntity.getBlockState()); -@@ -41,8 +62,8 @@ public abstract class CraftBlockEntityState extends Craft +@@ -41,8 +68,8 @@ public abstract class CraftBlockEntityState extends Craft try { // Paper - Show blockstate location if we failed to read it // Paper start - this.snapshotDisabled = DISABLE_SNAPSHOT; - if (DISABLE_SNAPSHOT) { -+ this.snapshotDisabled = getDisableSnapshotTL(); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) -+ if (this.snapshotDisabled) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) ++ this.snapshotDisabled = getDisableSnapshotTL(); // Leaf - SparklyPaper - parallel world ticking ++ if (this.snapshotDisabled) { // Leaf - SparklyPaper - parallel world ticking this.snapshot = this.blockEntity; } else { this.snapshot = this.createSnapshot(blockEntity); diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -index 196835bdf95ba0e149b2977e9ef41698971f501f..eb7e63d4549e672ff1206055d2d754395f189a4a 100644 +index 196835bdf95ba0e149b2977e9ef41698971f501f..7c40462a484776dfbff983a3636dec41479125e9 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java @@ -218,6 +218,12 @@ public class CraftBlockState implements BlockState { LevelAccessor access = this.getWorldHandle(); CraftBlock block = this.getBlock(); -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && access instanceof net.minecraft.server.level.ServerLevel serverWorld) { -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); ++ // Leaf start - SparklyPaper - parallel world ticking ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && access instanceof net.minecraft.server.level.ServerLevel serverLevel) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverLevel, position, "Cannot modify world asynchronously"); + } -+ // SparklyPaper end - parallel world ticking ++ // Leaf end - SparklyPaper - parallel world ticking + if (block.getType() != this.getType()) { if (!force) { return false; -@@ -365,6 +371,8 @@ public class CraftBlockState implements BlockState { +@@ -365,6 +371,7 @@ public class CraftBlockState implements BlockState { @Override public java.util.Collection getDrops(org.bukkit.inventory.ItemStack item, org.bukkit.entity.Entity entity) { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(world.getHandle(), position, "Cannot modify world asynchronously"); // SparklyPaper - parallel world ticking ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(world.getHandle(), position, "Cannot modify world asynchronously"); // Leaf - SparklyPaper - parallel world ticking this.requirePlaced(); net.minecraft.world.item.ItemStack nms = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item); diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java -index 18f09de5c6549df3562e710ede825f75d69c046e..1b06f97caeda6f33938ff5391ecaad5a1fc26f36 100644 +index 18f09de5c6549df3562e710ede825f75d69c046e..80ee9fdd9e3dd163801ada06ab4d8b8d062c8b0c 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java @@ -195,14 +195,14 @@ public final class CraftBlockStates { @@ -1541,60 +1701,66 @@ index 18f09de5c6549df3562e710ede825f75d69c046e..1b06f97caeda6f33938ff5391ecaad5a BlockEntity blockEntity = craftBlock.getHandle().getBlockEntity(pos); - boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT; - CraftBlockEntityState.DISABLE_SNAPSHOT = !useSnapshot; -+ boolean prev = CraftBlockEntityState.getDisableSnapshotTL(); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) -+ CraftBlockEntityState.setDisableSnapshotTL(!useSnapshot); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) ++ boolean prev = CraftBlockEntityState.getDisableSnapshotTL(); // Leaf - SparklyPaper - parallel world ticking ++ CraftBlockEntityState.setDisableSnapshotTL(!useSnapshot); // Leaf - SparklyPaper - parallel world ticking try { CraftBlockState blockState = CraftBlockStates.getBlockState(world, pos, state, blockEntity); blockState.setWorldHandle(craftBlock.getHandle()); // Inject the block's generator access return blockState; } finally { - CraftBlockEntityState.DISABLE_SNAPSHOT = prev; -+ CraftBlockEntityState.setDisableSnapshotTL(prev); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) ++ CraftBlockEntityState.setDisableSnapshotTL(prev); // Leaf - SparklyPaper - parallel world ticking } } diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index b4ee0f809c1524c74eca74ee6bc471a3051d92a6..96177467807e75bacb8c7c11ba7263f89bc0933a 100644 +index b4ee0f809c1524c74eca74ee6bc471a3051d92a6..e9b7291f7a52bf80c8149eb038fae3f32c7f4983 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -829,6 +829,28 @@ public class CraftEventFactory { +@@ -829,6 +829,34 @@ public class CraftEventFactory { } public static BlockPos sourceBlockOverride = null; // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPos up to five methods deep. -+ public static final ThreadLocal sourceBlockOverrideRT = new ThreadLocal<>(); // SparklyPaper - parallel world ticking (this is from Folia, fixes concurrency bugs with sculk catalysts) ++ private static final ThreadLocal SOURCE_BLOCK_OVERRIDE_RT = new ThreadLocal<>(); // Leaf - SparklyPaper - parallel world ticking (this is from Folia, fixes concurrency bugs with sculk catalysts) + -+ // Leaf start - SparklyPaper - parallel world ticking mod ++ // Leaf start - SparklyPaper - parallel world ticking + // refer to original field in case plugins attempt to modify it + public static BlockPos getSourceBlockOverrideRT() { -+ BlockPos sourceBlockOverrideRTCopy; -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && (sourceBlockOverrideRTCopy = sourceBlockOverrideRT.get()) != null) -+ return sourceBlockOverrideRTCopy; -+ synchronized (CraftEventFactory.class) { ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ final BlockPos sourceBlockOverrideRTCopy = SOURCE_BLOCK_OVERRIDE_RT.get(); ++ if (sourceBlockOverrideRTCopy != null) return sourceBlockOverrideRTCopy; ++ synchronized (CraftEventFactory.class) { ++ return sourceBlockOverride; ++ } ++ } else { + return sourceBlockOverride; + } + } + + // update original field in case plugins attempt to access it + public static void setSourceBlockOverrideRT(BlockPos value) { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) -+ sourceBlockOverrideRT.set(value); -+ synchronized (CraftEventFactory.class) { ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ SOURCE_BLOCK_OVERRIDE_RT.set(value); ++ synchronized (CraftEventFactory.class) { ++ sourceBlockOverride = value; ++ } ++ } else { + sourceBlockOverride = value; + } + } -+ // Leaf end - SparklyPaper - parallel world ticking mod ++ // Leaf end - SparklyPaper - parallel world ticking public static boolean handleBlockSpreadEvent(LevelAccessor world, BlockPos source, BlockPos target, net.minecraft.world.level.block.state.BlockState state, int flags) { return handleBlockSpreadEvent(world, source, target, state, flags, false); -@@ -844,7 +866,10 @@ public class CraftEventFactory { +@@ -844,7 +872,10 @@ public class CraftEventFactory { CraftBlockState snapshot = CraftBlockStates.getBlockState(world, target); snapshot.setData(state); - BlockSpreadEvent event = new BlockSpreadEvent(snapshot.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverride != null ? CraftEventFactory.sourceBlockOverride : source), snapshot); -+ // Leaf start - SparklyPaper parallel world ticking mod (collapse original behavior) ++ // Leaf start - SparklyPaper parallel world ticking + final BlockPos sourceBlockOverrideRTSnap = getSourceBlockOverrideRT(); -+ BlockSpreadEvent event = new BlockSpreadEvent(snapshot.getBlock(), CraftBlock.at(world, sourceBlockOverrideRTSnap != null ? sourceBlockOverrideRTSnap : source), snapshot); // SparklyPaper - parallel world ticking -+ // Leaf end - SparklyPaper parallel world ticking mod (collapse original behavior) ++ BlockSpreadEvent event = new BlockSpreadEvent(snapshot.getBlock(), CraftBlock.at(world, sourceBlockOverrideRTSnap != null ? sourceBlockOverrideRTSnap : source), snapshot); ++ // Leaf end - SparklyPaper parallel world ticking if (event.callEvent()) { boolean result = snapshot.place(flags); return !checkSetResult || result; diff --git a/leaf-server/paper-patches/features/0047-Fish-Parallel-World-Ticking-API.patch b/leaf-server/paper-patches/features/0047-Fish-Parallel-World-Ticking-API.patch index b4597a6d..d43e6696 100644 --- a/leaf-server/paper-patches/features/0047-Fish-Parallel-World-Ticking-API.patch +++ b/leaf-server/paper-patches/features/0047-Fish-Parallel-World-Ticking-API.patch @@ -32,10 +32,10 @@ index 6d721742598f625bcf589491b2b59dbbf5f8c903..c3ee0170aa8d74a1f0089d73d9df7e6f + // Fish end - Parallel World Ticking API } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index ee5f342995a335593932a497c2bafd36d34cecb2..a16390fc13e1baf3cffbcfec5cc410a72ed47367 100644 +index efffc80a44f626718e6969850ac5dfc48aec4547..abbbfc2afba2a0580537c49d40bd8b044dcdbfa7 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -2556,4 +2556,15 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -2552,4 +2552,15 @@ public class CraftWorld extends CraftRegionAccessor implements World { return POINTERS_SUPPLIER.view(this); } // Paper end diff --git a/leaf-server/paper-patches/features/0057-optimize-mob-spawning.patch b/leaf-server/paper-patches/features/0057-optimize-mob-spawning.patch index 3139bce1..e2c796cd 100644 --- a/leaf-server/paper-patches/features/0057-optimize-mob-spawning.patch +++ b/leaf-server/paper-patches/features/0057-optimize-mob-spawning.patch @@ -48,7 +48,7 @@ index c3ee0170aa8d74a1f0089d73d9df7e6f049eb067..7a5bda878ff77d20a9ea11e8ac879ceb @Override diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index a16390fc13e1baf3cffbcfec5cc410a72ed47367..ccda6365718b94a475ee68ff8ad4032101222a7c 100644 +index abbbfc2afba2a0580537c49d40bd8b044dcdbfa7..afbd170c766206e0ad58a59c828128317d7b853b 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -173,7 +173,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { @@ -72,7 +72,7 @@ index a16390fc13e1baf3cffbcfec5cc410a72ed47367..ccda6365718b94a475ee68ff8ad40321 if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { setSpawnLimit(spawnCategory, this.world.paperConfig().entities.spawning.spawnLimits.getInt(CraftSpawnCategory.toNMS(spawnCategory))); } -@@ -1868,7 +1870,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1865,7 +1867,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { return this.getSpawnLimitUnsafe(spawnCategory); } public final int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) { @@ -81,7 +81,7 @@ index a16390fc13e1baf3cffbcfec5cc410a72ed47367..ccda6365718b94a475ee68ff8ad40321 if (limit < 0) { limit = this.server.getSpawnLimitUnsafe(spawnCategory); // Paper end - Add mobcaps commands -@@ -1881,7 +1883,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1878,7 +1880,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null"); Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory.%s are not supported", spawnCategory);