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