diff --git a/build-data/leaf.at b/build-data/leaf.at index 5638508d..ff42ee40 100644 --- a/build-data/leaf.at +++ b/build-data/leaf.at @@ -17,6 +17,7 @@ public net.minecraft.world.entity.decoration.ArmorStand noTickEquipmentDirty public net.minecraft.world.entity.monster.Shulker MAX_SCALE public net.minecraft.world.entity.player.Player canGlide()Z public net.minecraft.world.item.CrossbowItem getShotPitch(Lnet/minecraft/util/RandomSource;I)F +public net.minecraft.world.level.block.PoweredRailBlock findPoweredRailSignal(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;ZI)Z public net.minecraft.world.level.block.entity.FuelValues values public net.minecraft.world.level.chunk.PaletteResize public net.minecraft.world.level.chunk.storage.RegionFile getOversizedData(II)Lnet/minecraft/nbt/CompoundTag; diff --git a/build.gradle.kts b/build.gradle.kts index d7e9534f..9773a231 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,7 +35,7 @@ subprojects { options.release = 21 options.isFork = true options.compilerArgs.addAll(listOf("-Xlint:-deprecation", "-Xlint:-removal")) - options.forkOptions.memoryMaximumSize = "6g" // Prevent OOM during building + options.forkOptions.memoryMaximumSize = "2g" // Prevent OOM during building } tasks.withType { options.encoding = Charsets.UTF_8.name() diff --git a/leaf-archived-patches/removed/hardfork/paperserver/0002-Leaf-Bootstrap.patch b/leaf-archived-patches/removed/hardfork/paperserver/0002-Leaf-Bootstrap.patch new file mode 100644 index 00000000..049a33d3 --- /dev/null +++ b/leaf-archived-patches/removed/hardfork/paperserver/0002-Leaf-Bootstrap.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Wed, 31 Jul 2024 22:05:21 +0800 +Subject: [PATCH] Leaf Bootstrap + +Removed since Leaf 1.21.4, useless + +org.bukkit.craftbukkit.Main#main -> LeafBootstrap -> PaperBootstrap -> ... + +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index ecb0fcd1f3b3f3d7751eded3cdf0977c1889c9ed..d0becb56a9911ef4cc55ae8d7c47832f442ad52f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -278,7 +278,8 @@ public class Main { + System.setProperty("jdk.console", "java.base"); // Paper - revert default console provider back to java.base so we can have our own jline + //System.out.println("Loading libraries, please wait..."); + //net.minecraft.server.Main.main(options); +- io.papermc.paper.PaperBootstrap.boot(options); ++ //io.papermc.paper.PaperBootstrap.boot(options); // Leaf - Leaf Boostrap - diff on change ++ org.dreeam.leaf.LeafBootstrap.boot(options); // Leaf - Leaf Boostrap + } catch (Throwable t) { + t.printStackTrace(); + } diff --git a/leaf-archived-patches/work/server/0208-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch b/leaf-archived-patches/removed/hardfork/server/0124-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch similarity index 94% rename from leaf-archived-patches/work/server/0208-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch rename to leaf-archived-patches/removed/hardfork/server/0124-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch index 36b4be10..ffea6692 100644 --- a/leaf-archived-patches/work/server/0208-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch +++ b/leaf-archived-patches/removed/hardfork/server/0124-ShreddedPaper-Don-t-block-main-thread-in-Connection-.patch @@ -4,6 +4,7 @@ Date: Thu, 1 Aug 2024 00:43:05 +0900 Subject: [PATCH] ShreddedPaper: Don't block main thread in Connection#syncAfterConfigurationChange +Removed since Leaf 1.21.4, replaced by async config switch diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java index 00a82873d226f113278632a53c0faca420dd67d4..5b46036868b6c9d082e35591e58735e16adaae62 100644 diff --git a/leaf-archived-patches/work/server/0066-Moonrise-Optimise-checkInsideBlocks.patch b/leaf-archived-patches/removed/legacy/server/0066-Moonrise-Optimise-checkInsideBlocks.patch similarity index 99% rename from leaf-archived-patches/work/server/0066-Moonrise-Optimise-checkInsideBlocks.patch rename to leaf-archived-patches/removed/legacy/server/0066-Moonrise-Optimise-checkInsideBlocks.patch index a744fdeb..b2ea1d47 100644 --- a/leaf-archived-patches/work/server/0066-Moonrise-Optimise-checkInsideBlocks.patch +++ b/leaf-archived-patches/removed/legacy/server/0066-Moonrise-Optimise-checkInsideBlocks.patch @@ -3,6 +3,8 @@ From: Spottedleaf Date: Thu, 5 Sep 2024 15:42:15 -0700 Subject: [PATCH] Moonrise: Optimise checkInsideBlocks +Removed since Leaf 1.21.3, Optimized in Minecraft + Original license: GPLv3 Original project: https://github.com/Tuinity/Moonrise diff --git a/leaf-archived-patches/work/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch b/leaf-archived-patches/removed/legacy/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch similarity index 99% rename from leaf-archived-patches/work/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch rename to leaf-archived-patches/removed/legacy/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch index 06f5f781..71a0b9ff 100644 --- a/leaf-archived-patches/work/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch +++ b/leaf-archived-patches/removed/legacy/server/0067-Moonrise-Avoid-streams-for-block-retrieval-in-Entity.patch @@ -3,6 +3,8 @@ From: Spottedleaf Date: Thu, 5 Sep 2024 16:23:04 -0700 Subject: [PATCH] Moonrise: Avoid streams for block retrieval in Entity#move +Removed since Leaf 1.21.3, Optimized in Minecraft + Original license: GPLv3 Original project: https://github.com/Tuinity/Moonrise diff --git a/leaf-archived-patches/work/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch b/leaf-archived-patches/removed/legacy/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch similarity index 99% rename from leaf-archived-patches/work/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch rename to leaf-archived-patches/removed/legacy/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch index 5bd6c56d..fcaba870 100644 --- a/leaf-archived-patches/work/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch +++ b/leaf-archived-patches/removed/legacy/server/0150-Use-MCUtil.asyncExecutor-for-MAIN_WORKER_EXECUTOR.patch @@ -3,6 +3,7 @@ From: Taiyou06 Date: Fri, 8 Nov 2024 00:54:42 +0100 Subject: [PATCH] Use MCUtil.asyncExecutor for MAIN_WORKER_EXECUTOR +Removed since Leaf 1.21.3 diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java index 815253d03b85a7a476c1efdeca9496fd64afc137..bb4c9bbebaefe9a0c7d213e9b2b07308e684dc7c 100644 diff --git a/leaf-archived-patches/unapplied/api/paper-patches/features/1009-Leaves-Replay-Mod-API.patch b/leaf-archived-patches/unapplied/api/paper-patches/features/0009-Leaves-Replay-Mod-API.patch similarity index 100% rename from leaf-archived-patches/unapplied/api/paper-patches/features/1009-Leaves-Replay-Mod-API.patch rename to leaf-archived-patches/unapplied/api/paper-patches/features/0009-Leaves-Replay-Mod-API.patch diff --git a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0025-Leaves-Replay-Mod-API.patch b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0025-Leaves-Replay-Mod-API.patch index 811bd094..e1f45d70 100644 --- a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0025-Leaves-Replay-Mod-API.patch +++ b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0025-Leaves-Replay-Mod-API.patch @@ -105,7 +105,7 @@ index dddbb18992348fb7e8a6552423d134809cd7fdbc..0e6e71030e3fd1335fff796b861524a4 if (this.hidesOnlinePlayers()) { return new ServerStatus.Players(maxPlayers, players.size(), List.of()); diff --git a/net/minecraft/server/PlayerAdvancements.java b/net/minecraft/server/PlayerAdvancements.java -index 792ba93b531e9586e26aafa00830022a8996fc04..e4ea26ae84efde7ce54e08a246a6ea2ae2a17151 100644 +index abccabb8a0a1a9730b7df070dd25f3ca215af362..0a4bcc4c44fed2ededafaf0641315e072b7ba771 100644 --- a/net/minecraft/server/PlayerAdvancements.java +++ b/net/minecraft/server/PlayerAdvancements.java @@ -168,6 +168,11 @@ public class PlayerAdvancements { @@ -134,7 +134,7 @@ index 5c0a04db38821dbb0cba2bb6f0787f113d167efd..cd153db93f709c3142942fac88ae3ca2 .filter(player -> !playerList.isOp(player.getGameProfile())) .map(player -> player.getGameProfile().getName()), diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 94ee31a4a02edb003b98a09b0311355c1db4f547..f8bd39ddd7b6948734254acfb8b0235eff774133 100644 +index b49dd636e730f0c5b609df68ee51bcd12efc1eaa..5e971bca365c692d4ce0c58693592002ce01471c 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -216,6 +216,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -153,7 +153,7 @@ index 94ee31a4a02edb003b98a09b0311355c1db4f547..f8bd39ddd7b6948734254acfb8b0235e } // Paper start -@@ -2672,6 +2674,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2698,6 +2700,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true if (entity instanceof ServerPlayer serverPlayer) { ServerLevel.this.players.add(serverPlayer); @@ -165,7 +165,7 @@ index 94ee31a4a02edb003b98a09b0311355c1db4f547..f8bd39ddd7b6948734254acfb8b0235e ServerLevel.this.updateSleepingPlayerList(); } -@@ -2742,6 +2749,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2768,6 +2775,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe ServerLevel.this.getChunkSource().removeEntity(entity); if (entity instanceof ServerPlayer serverPlayer) { ServerLevel.this.players.remove(serverPlayer); @@ -178,7 +178,7 @@ index 94ee31a4a02edb003b98a09b0311355c1db4f547..f8bd39ddd7b6948734254acfb8b0235e } diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index 622257dbbe572de33e15abef9055016268730261..dfb4524d80f642eff1b146dd2fbfa07f21d844c6 100644 +index d44c3baa2ef30d5cd4c46e491ff9198fa558513c..f89d28595fa9ca12e414f7b3cc86085ff0769e72 100644 --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java @@ -195,7 +195,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc @@ -191,7 +191,7 @@ index 622257dbbe572de33e15abef9055016268730261..dfb4524d80f642eff1b146dd2fbfa07f private final ServerStatsCounter stats; private float lastRecordedHealthAndAbsorption = Float.MIN_VALUE; diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java -index e3d09d5f4efb32bb276e001e5ee747a775b502ee..78b15d750d75e5d4c2318a3a18e83afdd5f4fbe1 100644 +index c26bf04abe86b566e7f5cd29191a0a853f9808f8..4c172e2aee3e48d42009cd39b28f694aa71e20e3 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java @@ -132,6 +132,7 @@ public abstract class PlayerList { diff --git a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch index ff920aca..d9544927 100644 --- a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch +++ b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0134-SparklyPaper-Parallel-world-ticking.patch @@ -265,7 +265,7 @@ index 5ab2c8333178335515e619b87ae420f948c83bd1..be3b2f023897a8823560ee059cb16ec9 } diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index c50a301a0c2365c2052aefc6a23fcf6fa82e1b9d..ac751d460ae0c8dbb858c4047c459a11b57ae175 100644 +index c50a301a0c2365c2052aefc6a23fcf6fa82e1b9d..7f791ff5bdf6c29c78f863d21af16270a74d8e7e 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -291,6 +291,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop serverPlayer1.connection.suspendFlushing()); this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit -@@ -1743,28 +1768,50 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop, ServerLevel> oldLevels = this.levels; Map, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels); newLevels.remove(level.dimension()); @@ -568,7 +581,7 @@ index d4048661575ebfaf128ba25da365843774364e0e..33dd16a26edd2974f04d9a868d3e58e8 // Gale start - Pufferfish - SIMD support diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index a66e5f6652d9633c856490de36d8d8fdf8a5298a..60e0296312030d25f917c568c17ce86d08e18122 100644 +index a66e5f6652d9633c856490de36d8d8fdf8a5298a..d6524d5c442555eaeb4d90f6a101262ee669f0d9 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -182,7 +182,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -580,23 +593,173 @@ index a66e5f6652d9633c856490de36d8d8fdf8a5298a..60e0296312030d25f917c568c17ce86d // Paper - rewrite chunk system private final GameEventDispatcher gameEventDispatcher; public boolean noSave; -@@ -208,6 +208,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -208,7 +208,9 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe private double preciseTime; // Purpur - Configurable daylight cycle private boolean forceTime; // Purpur - Configurable daylight cycle private final RandomSequences randomSequences; +- + public java.util.concurrent.ExecutorService tickExecutor; // SparklyPaper - parallel world ticking - ++ public final java.util.concurrent.ConcurrentLinkedQueue asyncReadRequestQueue = new java.util.concurrent.ConcurrentLinkedQueue<>(); // Leaf - SparklyPaper - parallel world ticking ++ private volatile boolean isShuttingDown = false; // Leaf - SparklyPaper - parallel world ticking - Shutdown handling for async reads // CraftBukkit start public final LevelStorageSource.LevelStorageAccess levelStorageAccess; -@@ -703,6 +704,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + public final UUID uuid; +@@ -703,8 +705,26 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle this.realPlayers = Lists.newArrayList(); // Leaves - skip + this.tickExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new org.dreeam.leaf.async.world.SparklyPaperServerLevelTickExecutorThreadFactory(getWorld().getName())); // SparklyPaper - parallel world ticking ++ } ++ ++ // Leaf start - SparklyPaper - parallel world ticking - Shutdown handling for async reads ++ public boolean isShuttingDown() { ++ return this.isShuttingDown; } ++ public void prepareShutdown() { ++ this.isShuttingDown = true; ++ org.dreeam.leaf.async.world.WorldReadRequest req; ++ int clearedRequests = 0; ++ while ((req = this.asyncReadRequestQueue.poll()) != null) { ++ req.future().completeExceptionally(new IllegalStateException("World " + this.getWorld().getName() + " is shutting down. Cannot process buffered read: " + req.type())); ++ clearedRequests++; ++ } ++ if (clearedRequests > 0) MinecraftServer.LOGGER.info("PWT: Cleared " + clearedRequests + " pending async read requests for world " + this.getWorld().getName() + " during shutdown."); ++ } ++ // Leaf end - SparklyPaper - parallel world ticking - Shutdown handling for async reads ++ // Paper start -@@ -1313,9 +1315,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + @Override + public boolean hasChunk(int chunkX, int chunkZ) { +@@ -737,8 +757,112 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + public Player[] eligibleDespawnCheckingPlayerCache = new Player[0]; // Leaf - Cache eligible players for despawn checks + ++ // Leaf start - SparklyPaper - parallel world ticking ++ private void processAsyncReadRequests() { ++ // Only process if parallel ticking is enabled and buffering is active ++ if (!org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled || ++ !org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling.equals("BUFFERED")) { ++ // Clear queue if buffering gets disabled to prevent memory leaks ++ if (!this.asyncReadRequestQueue.isEmpty()) { ++ org.dreeam.leaf.async.world.WorldReadRequest req; ++ while ((req = this.asyncReadRequestQueue.poll()) != null) { ++ req.future().completeExceptionally(new IllegalStateException("Async read buffering disabled while request was pending.")); ++ } ++ } ++ return; ++ } ++ ++ org.dreeam.leaf.async.world.WorldReadRequest request; ++ int processed = 0; ++ // Limit processing per tick to avoid stalling the tick loop ++ int maxToProcess = 16384; // Consider making this configurable ++ ++ while (processed < maxToProcess && (request = this.asyncReadRequestQueue.poll()) != null) { ++ processed++; ++ // Ensure we are on the correct thread before executing ++ // This check might be redundant if called correctly from tick(), but good for safety ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Processing async read request off-thread"); ++ ++ try { ++ Object result = executeReadRequest(request); ++ request.future().complete(result); ++ } catch (Throwable t) { ++ // Log the error from the tick thread side ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Exception processing buffered async world read for type " + request.type(), t); ++ request.future().completeExceptionally(t); ++ } ++ } ++ } ++ ++ // Executes the actual read operation based on the request type ++ private Object executeReadRequest(org.dreeam.leaf.async.world.WorldReadRequest request) { ++ Object[] params = request.params(); ++ BlockPos pos; // Declare pos outside the switch ++ ++ switch (request.type()) { ++ case BLOCK_GET_NMS_STATE: { // ++ pos = (BlockPos) params[0]; ++ return this.getBlockState(pos); ++ } ++ case BLOCK_GET_BIOME: { ++ pos = (BlockPos) params[0]; ++ return this.getNoiseBiome(pos.getX() >> 2, pos.getY() >> 2, pos.getZ() >> 2); ++ } ++ case BLOCK_GET_COMPUTED_BIOME: { ++ pos = (BlockPos) params[0]; ++ return this.getBiome(pos); ++ } ++ case BLOCK_IS_INDIRECTLY_POWERED: { ++ pos = (BlockPos) params[0]; ++ return this.hasNeighborSignal(pos); ++ } ++ case BLOCK_GET_BLOCK_POWER: { ++ pos = (BlockPos) params[0]; ++ org.bukkit.block.BlockFace face = (org.bukkit.block.BlockFace) params[1]; ++ int power = 0; ++ Direction notchDir = org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(face); ++ ++ if ((face == org.bukkit.block.BlockFace.DOWN || face == org.bukkit.block.BlockFace.SELF) && this.hasSignal(pos.below(), Direction.DOWN)) power = org.bukkit.craftbukkit.block.CraftBlock.getPower(power, this.getBlockState(pos.below())); ++ if ((face == org.bukkit.block.BlockFace.UP || face == org.bukkit.block.BlockFace.SELF) && this.hasSignal(pos.above(), Direction.UP)) power = org.bukkit.craftbukkit.block.CraftBlock.getPower(power, this.getBlockState(pos.above())); ++ if ((face == org.bukkit.block.BlockFace.EAST || face == org.bukkit.block.BlockFace.SELF) && this.hasSignal(pos.east(), Direction.EAST)) power = org.bukkit.craftbukkit.block.CraftBlock.getPower(power, this.getBlockState(pos.east())); ++ if ((face == org.bukkit.block.BlockFace.WEST || face == org.bukkit.block.BlockFace.SELF) && this.hasSignal(pos.west(), Direction.WEST)) power = org.bukkit.craftbukkit.block.CraftBlock.getPower(power, this.getBlockState(pos.west())); ++ if ((face == org.bukkit.block.BlockFace.NORTH || face == org.bukkit.block.BlockFace.SELF) && this.hasSignal(pos.north(), Direction.NORTH)) power = org.bukkit.craftbukkit.block.CraftBlock.getPower(power, this.getBlockState(pos.north())); ++ if ((face == org.bukkit.block.BlockFace.SOUTH || face == org.bukkit.block.BlockFace.SELF) && this.hasSignal(pos.south(), Direction.SOUTH)) power = org.bukkit.craftbukkit.block.CraftBlock.getPower(power, this.getBlockState(pos.south())); ++ ++ boolean indirectlyPowered = (face == org.bukkit.block.BlockFace.SELF) ? this.hasNeighborSignal(pos) : (this.getSignal(pos, notchDir) > 0); // Simplified indirect check for faces ++ return power > 0 ? power : (indirectlyPowered ? 15 : 0); ++ } ++ case BLOCK_RAY_TRACE: { ++ pos = (BlockPos) params[0]; ++ org.bukkit.Location start = (org.bukkit.Location) params[1]; ++ org.bukkit.util.Vector direction = (org.bukkit.util.Vector) params[2]; ++ double maxDistance = (double) params[3]; ++ org.bukkit.FluidCollisionMode fluidCollisionMode = (org.bukkit.FluidCollisionMode) params[4]; ++ ++ org.bukkit.util.Vector dir = direction.clone().normalize().multiply(maxDistance); ++ Vec3 startPos = org.bukkit.craftbukkit.util.CraftLocation.toVec3D(start); ++ Vec3 endPos = startPos.add(dir.getX(), dir.getY(), dir.getZ()); ++ ++ return this.clip(new net.minecraft.world.level.ClipContext(startPos, endPos, net.minecraft.world.level.ClipContext.Block.OUTLINE, org.bukkit.craftbukkit.CraftFluidCollisionMode.toNMS(fluidCollisionMode), net.minecraft.world.phys.shapes.CollisionContext.empty()), pos); // Pass block pos ++ } ++ case BLOCK_CAN_PLACE: { ++ pos = (BlockPos) params[0]; ++ org.bukkit.block.data.BlockData data = (org.bukkit.block.data.BlockData) params[1]; ++ net.minecraft.world.level.block.state.BlockState nmsData = ((org.bukkit.craftbukkit.block.data.CraftBlockData) data).getState(); ++ return nmsData.canSurvive(this, pos); ++ } ++ // Add cases for other ReadOperationType values here... ++ // case GET_ENTITIES_IN_BOX: ... (complex, needs careful list handling) ++ ++ default: ++ throw new UnsupportedOperationException("Unsupported buffered read type: " + request.type()); ++ } ++ } ++ // Leaf end - SparklyPaper - parallel world ticking ++ + public void tick(BooleanSupplier hasTimeLeft) { + this.handlingTick = true; ++ this.processAsyncReadRequests(); // Leaf - SparklyPaper - parallel world ticking + TickRateManager tickRateManager = this.tickRateManager(); + boolean runsNormally = tickRateManager.runsNormally(); + if (runsNormally) { +@@ -746,6 +870,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.advanceWeatherCycle(); + } + ++ // Leaf start - SparklyPaper - parallel world ticking ++ if (!org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ this.moonrise$midTickTasks(); ++ } else if ((++this.tickedBlocksOrFluids & 7L) != 0L) { // Keep original mid-tick logic for PWT enabled ++ this.server.moonrise$executeMidTickTasks(); ++ } ++ // Leaf end - SparklyPaper - parallel world ticking ++ + int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); + if (this.purpurConfig.playersSkipNight && this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) { // Purpur - Config for skipping night + // Paper start - create time skip event - move up calculations +@@ -1313,9 +1445,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe fluidState.tick(this, pos, blockState); } // Paper start - rewrite chunk system @@ -611,7 +774,7 @@ index a66e5f6652d9633c856490de36d8d8fdf8a5298a..60e0296312030d25f917c568c17ce86d // Paper end - rewrite chunk system } -@@ -1326,9 +1331,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1326,9 +1461,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe blockState.tick(this, pos, this.random); } // Paper start - rewrite chunk system @@ -626,7 +789,7 @@ index a66e5f6652d9633c856490de36d8d8fdf8a5298a..60e0296312030d25f917c568c17ce86d // Paper end - rewrite chunk system } -@@ -1579,6 +1587,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1579,6 +1717,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } private void addPlayer(ServerPlayer player) { @@ -635,7 +798,7 @@ index a66e5f6652d9633c856490de36d8d8fdf8a5298a..60e0296312030d25f917c568c17ce86d Entity entity = this.getEntities().get(player.getUUID()); if (entity != null) { LOGGER.warn("Force-added player with duplicate UUID {}", player.getUUID()); -@@ -1591,7 +1601,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1591,7 +1731,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // CraftBukkit start private boolean addEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { diff --git a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0135-SparklyPaper-Track-each-world-MSPT.patch b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0135-SparklyPaper-Track-each-world-MSPT.patch index aef67f40..f16beee9 100644 --- a/leaf-archived-patches/unapplied/server/minecraft-patches/features/0135-SparklyPaper-Track-each-world-MSPT.patch +++ b/leaf-archived-patches/unapplied/server/minecraft-patches/features/0135-SparklyPaper-Track-each-world-MSPT.patch @@ -6,10 +6,10 @@ Subject: [PATCH] SparklyPaper: Track each world MSPT Original project: https://github.com/SparklyPower/SparklyPaper diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index ac751d460ae0c8dbb858c4047c459a11b57ae175..24926aa7ed5c78b235659daf18b224b14beb744c 100644 +index 7f791ff5bdf6c29c78f863d21af16270a74d8e7e..1431a0fac3c8a846535c1bd2f60a1279d08f14ea 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -1693,7 +1693,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Wed, 5 Mar 2025 13:16:44 -0800 +Subject: [PATCH] SparklyPaper: Parallel world ticking + +Original project: https://github.com/SparklyPower/SparklyPaper + +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 a4aa2615823d77920ff55b8aa0bcc27a54b8c3e1..2fb65ce228da94eb7d9364ee0f94582300178f1d 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 String getThreadContext() { + return "thread=" + Thread.currentThread().getName(); +@@ -26,14 +27,14 @@ public class TickThread extends Thread { + public static void ensureTickThread(final String reason) { + if (!isTickThread()) { + LOGGER.error("Thread failed main thread check: " + reason + ", context=" + getThreadContext(), new Throwable()); +- throw new IllegalStateException(reason); ++ if (HARD_THROW) throw new IllegalStateException(reason); // SparklyPaper - parallel world ticking + } + } + + public static void ensureTickThread(final Level world, final BlockPos pos, final String reason) { + 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()); + throw new IllegalStateException(ex); + } +@@ -42,7 +43,7 @@ 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()); + throw new IllegalStateException(ex); + } +@@ -51,7 +52,7 @@ 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()); + throw new IllegalStateException(ex); + } +@@ -60,7 +61,7 @@ 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()); + throw new IllegalStateException(ex); + } +@@ -69,7 +70,7 @@ 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()); + throw new IllegalStateException(ex); + } +@@ -78,7 +79,7 @@ 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()); + throw new IllegalStateException(ex); + } +@@ -87,12 +88,70 @@ 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()); + throw new IllegalStateException(ex); + } + } + ++ // SparklyPaper start - 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 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); ++ } ++ } ++ ++ // 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); ++ } ++ } ++ ++ // 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); ++ } ++ } ++ ++ public static String getTickThreadInformation(net.minecraft.server.MinecraftServer minecraftServer) { ++ StringBuilder sb = new StringBuilder(); ++ Thread currentThread = Thread.currentThread(); ++ sb.append("Is tick thread? "); ++ sb.append(currentThread instanceof TickThread); ++ sb.append("; Is server level tick thread? "); ++ 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("; Is iterating over levels? "); ++ sb.append(minecraftServer.isIteratingOverLevels); ++ sb.append("; Are we going to hard throw? "); ++ sb.append(HARD_THROW); ++ return sb.toString(); ++ } ++ ++ public static boolean isServerLevelTickThread() { ++ return Thread.currentThread() instanceof ServerLevelTickThread; ++ } ++ // SparklyPaper end - 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(); +@@ -133,46 +192,74 @@ 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) + } + + 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) + } + + 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) + } + + 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) + } + + 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) + } + + 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) + } + + 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) + } + + 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) + } + + 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) + } + + 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) ++ } ++ ++ // SparklyPaper start - 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(); ++ } + } + + 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 ServerLevelTickThread(String name) { ++ super(name); ++ } ++ ++ public ServerLevelTickThread(Runnable run, String name) { ++ super(run, name); ++ } ++ ++ public net.minecraft.server.level.ServerLevel currentlyTickingServerLevel; + } ++ // SparklyPaper end - parallel world ticking + } +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +index 548fcd9646dee0c40b6ba9b3dafb9ca157dfe324..d7af94890bfccd6ff665d920cecfa1e5be626aa4 100644 +--- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +@@ -40,6 +40,12 @@ class PaperEventManager { + if (listeners.length == 0) return; + // Leaf end - Skip event if no listeners + if (event.isAsynchronous() && this.server.isPrimaryThread()) { ++ // Leaf start - Parallel world ticking ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.runAsyncTasksSync) { ++ org.dreeam.leaf.async.world.PWTEventScheduler.getScheduler().scheduleTask(event::callEvent); ++ return; ++ } ++ // Leaf end - Parallel world ticking + throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously."); + } else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) { + // Leaf start - Multithreaded tracker +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index af33cab59932f4ec135caf94dc5828930833daf6..caa92e48d031cb54950e6613a82f407d7ed2455a 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -455,7 +455,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { + } + + 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 ++ org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot ++ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) + if (!this.isChunkLoaded(x, z)) { + return true; + } +@@ -472,6 +477,8 @@ 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) + ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); + if (playerChunk == null) return false; + +@@ -522,7 +529,12 @@ 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 ++ org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot ++ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) + 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 + +@@ -750,6 +762,8 @@ 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) + this.world.captureTreeGeneration = true; + this.world.captureBlockStates = true; + boolean grownTree = this.generateTree(loc, type); +@@ -865,6 +879,8 @@ 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) + net.minecraft.world.level.Level.ExplosionInteraction explosionType; + if (!breakBlocks) { + explosionType = net.minecraft.world.level.Level.ExplosionInteraction.NONE; // Don't break blocks +@@ -986,6 +1002,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { + @Override + public void setBiome(int x, int y, int z, Holder bb) { + BlockPos pos = new BlockPos(x, 0, z); ++ 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 (this.world.hasChunkAt(pos)) { + net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkAt(pos); + +@@ -2328,6 +2346,8 @@ 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) + 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 811823a1a7e24a19a7e37eb4c08efdfa19e839ed..ac45c5cbe547705e3e341011740cf911c39f80c0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -74,13 +74,98 @@ 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; ++ } ++ ++ private boolean needsBuffering(ServerLevel level, String handlingMode) { ++ // No ServerLevel means no queue, can't buffer ++ return level != null && org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && handlingMode.equals("BUFFERED") && !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(level); ++ } ++ ++ private void checkStrictMode(ServerLevel level, String handlingMode, String methodName) { ++ // Only check if PWT enabled, mode is STRICT, and we have a ServerLevel ++ if (level != null && org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && handlingMode.equals("STRICT")) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, this.position, "PWT: Async unsafe block read (strict mode): " + methodName); ++ } ++ } ++ ++ private T executeBufferedRead(ServerLevel level, org.dreeam.leaf.async.world.ReadOperationType type, Object[] params, T defaultValue, String methodName) { ++ if (level == null) { // Should not happen if called correctly after needsBuffering ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: executeBufferedRead called with null ServerLevel for " + methodName); ++ return defaultValue; ++ } ++ ++ java.util.concurrent.CompletableFuture future = new java.util.concurrent.CompletableFuture<>(); ++ ++ // Leaf start - SparklyPaper - parallel world ticking - Shutdown handling for async reads ++ if (level.isShuttingDown()) { ++ future.completeExceptionally(new IllegalStateException("World " + level.getWorld().getName() + " is shutting down. Cannot queue new buffered read: " + type)); ++ } else { ++ org.dreeam.leaf.async.world.WorldReadRequest request = new org.dreeam.leaf.async.world.WorldReadRequest(type, params, future); ++ level.asyncReadRequestQueue.offer(request); // Assumes queue exists on ServerLevel ++ } ++ // Leaf end - SparklyPaper - parallel world ticking - Shutdown handling for async reads ++ ++ try { ++ Object result = future.join(); // Block until tick thread completes it ++ if (result == null) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Buffered async read returned null for " + methodName + " - returning default."); ++ return defaultValue; ++ } ++ return (T) result; ++ } catch (java.util.concurrent.CompletionException e) { ++ // Leaf start - SparklyPaper - parallel world ticking - Shutdown handling for async reads ++ if (e.getCause() instanceof IllegalStateException && e.getCause().getMessage() != null && e.getCause().getMessage().contains("shutting down")) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Async block read for " + methodName + " cancelled due to world shutdown: " + e.getCause().getMessage()); ++ } else { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Async block read failed for " + methodName + " on tick thread.", e.getCause() != null ? e.getCause() : e); ++ } ++ return defaultValue; // Return default or rethrow if appropriate for your error handling strategy ++ // Leaf end - SparklyPaper - parallel world ticking - Shutdown handling for async reads ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Unexpected error during async block read for " + methodName, e); ++ return defaultValue; ++ } ++ } ++ // Leaf end - SparklyPaper - parallel world ticking ++ + public net.minecraft.world.level.block.state.BlockState getNMS() { +- return this.world.getBlockState(this.position); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ ++ if (needsBuffering(level, handlingMode)) { ++ // Buffered path ++ return executeBufferedRead(level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_GET_NMS_STATE, new Object[]{this.position}, Blocks.AIR.defaultBlockState(), "getNMS"); ++ } else { ++ // Strict/Disabled/Non-ServerLevel path ++ checkStrictMode(level, handlingMode, "getNMS"); ++ try { ++ return this.world.getBlockState(this.position); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getNMS" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return Blocks.AIR.defaultBlockState(); ++ } ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + // Paper start + public net.minecraft.world.level.material.FluidState getNMSFluid() { +- return this.world.getFluidState(this.position); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "getNMSFluid"); ++ ++ try { ++ return this.world.getFluidState(this.position); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getNMSFluid" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return net.minecraft.world.level.material.Fluids.EMPTY.defaultFluidState(); ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + // Paper end + +@@ -144,10 +229,12 @@ public class CraftBlock implements Block { + return this.getWorld().getChunkAt(this); + } + ++ @Deprecated // Leaf - SparklyPaper - parallel world ticking - Magic value + public void setData(final byte data) { + this.setData(data, 3); + } + ++ @Deprecated // Leaf - SparklyPaper - parallel world ticking - Magic value + public void setData(final byte data, boolean applyPhysics) { + if (applyPhysics) { + this.setData(data, 3); +@@ -157,12 +244,18 @@ public class CraftBlock implements Block { + } + + private void setData(final byte data, int flag) { ++ // 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 + this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flag); + } + + @Override ++ @Deprecated // Leaf - SparklyPaper - parallel world ticking - Magic value + public byte getData() { +- net.minecraft.world.level.block.state.BlockState blockData = this.world.getBlockState(this.position); ++ net.minecraft.world.level.block.state.BlockState blockData = this.getNMS(); // Leaf - SparklyPaper - parallel world ticking + return CraftMagicNumbers.toLegacyData(blockData); + } + +@@ -179,6 +272,7 @@ public class CraftBlock implements Block { + @Override + public void setType(Material type, boolean applyPhysics) { + Preconditions.checkArgument(type != null, "Material cannot be null"); ++ // Leaf - SparklyPaper - parallel world ticking - Delegates to setBlockData, which delegates to setTypeAndData (which has checks) + this.setBlockData(type.createBlockData(), applyPhysics); + } + +@@ -198,6 +292,12 @@ public class CraftBlock implements Block { + } + + public static boolean setTypeAndData(LevelAccessor world, BlockPos position, net.minecraft.world.level.block.state.BlockState old, net.minecraft.world.level.block.state.BlockState blockData, boolean applyPhysics) { ++ // 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 ++ + // SPIGOT-611: need to do this to prevent glitchiness. Easier to handle this here (like /setblock) than to fix weirdness in tile entity cleanup + if (old.hasBlockEntity() && blockData.getBlock() != old.getBlock()) { // SPIGOT-3725 remove old tile entity if block changes + // SPIGOT-4612: faster - just clear tile +@@ -226,22 +326,62 @@ public class CraftBlock implements Block { + + @Override + public Material getType() { +- return this.world.getBlockState(this.position).getBukkitMaterial(); // Paper - optimise getType calls ++ return this.getNMS().getBukkitMaterial(); // Paper - optimise getType calls // Leaf - SparklyPaper - parallel world ticking + } + + @Override + public byte getLightLevel() { +- return (byte) this.world.getMinecraftWorld().getMaxLocalRawBrightness(this.position); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "getLightLevel"); // Strict check if applicable ++ ++ try { ++ // Requires Level for getMaxLocalRawBrightness ++ if (this.world instanceof net.minecraft.world.level.Level nmsLevel) { ++ return (byte) nmsLevel.getMaxLocalRawBrightness(this.position); ++ } ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: getLightLevel called on non-Level - returning 0"); ++ return 0; ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getLightLevel" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return 0; ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override + public byte getLightFromSky() { +- return (byte) this.world.getBrightness(LightLayer.SKY, this.position); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "getLightFromSky"); // Strict check if applicable ++ ++ try { ++ // LevelAccessor has getBrightness ++ return (byte) this.world.getBrightness(LightLayer.SKY, this.position); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getLightFromSky" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return 0; ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override + public byte getLightFromBlocks() { +- return (byte) this.world.getBrightness(LightLayer.BLOCK, this.position); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "getLightFromBlocks"); // Strict check if applicable ++ ++ try { ++ // LevelAccessor has getBrightness ++ return (byte) this.world.getBrightness(LightLayer.BLOCK, this.position); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getLightFromBlocks" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return 0; ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + public Block getFace(final BlockFace face) { +@@ -282,51 +422,37 @@ public class CraftBlock implements Block { + + @Override + public String toString() { +- return "CraftBlock{pos=" + this.position + ",type=" + this.getType() + ",data=" + this.getNMS() + ",fluid=" + this.world.getFluidState(this.position) + '}'; ++ return "CraftBlock{pos=" + this.position + ",type=" + this.getType() + ",data=" + this.getNMS() + ",fluid=" + this.getNMSFluid() + '}'; // Leaf - SparklyPaper - parallel world ticking + } + + public static BlockFace notchToBlockFace(Direction notch) { +- if (notch == null) { +- return BlockFace.SELF; +- } +- switch (notch) { +- case DOWN: +- return BlockFace.DOWN; +- case UP: +- return BlockFace.UP; +- case NORTH: +- return BlockFace.NORTH; +- case SOUTH: +- return BlockFace.SOUTH; +- case WEST: +- return BlockFace.WEST; +- case EAST: +- return BlockFace.EAST; +- default: +- return BlockFace.SELF; +- } ++ // Leaf start - SparklyPaper - parallel world ticking - formatting ++ if (notch == null) return BlockFace.SELF; ++ return switch (notch) { ++ case DOWN -> BlockFace.DOWN; ++ case UP -> BlockFace.UP; ++ case NORTH -> BlockFace.NORTH; ++ case SOUTH -> BlockFace.SOUTH; ++ case WEST -> BlockFace.WEST; ++ case EAST -> BlockFace.EAST; ++ default -> BlockFace.SELF; ++ }; ++ // Leaf end - SparklyPaper - parallel world ticking - formatting + } + + public static Direction blockFaceToNotch(BlockFace face) { +- if (face == null) { +- return null; +- } +- switch (face) { +- case DOWN: +- return Direction.DOWN; +- case UP: +- return Direction.UP; +- case NORTH: +- return Direction.NORTH; +- case SOUTH: +- return Direction.SOUTH; +- case WEST: +- return Direction.WEST; +- case EAST: +- return Direction.EAST; +- default: +- return null; +- } ++ // Leaf start - SparklyPaper - parallel world ticking - formatting ++ if (face == null) return null; ++ return switch (face) { ++ case DOWN -> Direction.DOWN; ++ case UP -> Direction.UP; ++ case NORTH -> Direction.NORTH; ++ case SOUTH -> Direction.SOUTH; ++ case WEST -> Direction.WEST; ++ case EAST -> Direction.EAST; ++ default -> null; ++ }; ++ // Leaf end - SparklyPaper - parallel world ticking - formatting + } + + @Override +@@ -343,18 +469,65 @@ public class CraftBlock implements Block { + + @Override + public Biome getBiome() { +- return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ ++ if (needsBuffering(level, handlingMode)) { ++ // Buffered path ++ net.minecraft.core.Holder nmsResult = executeBufferedRead( ++ level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_GET_BIOME, new Object[]{this.position}, null, "getBiome" ++ ); ++ return (nmsResult != null) ? CraftBiome.minecraftHolderToBukkit(nmsResult) : Biome.PLAINS; // Default biome ++ } else { ++ // Strict/Disabled/Non-ServerLevel path ++ checkStrictMode(level, handlingMode, "getBiome"); ++ try { ++ // Use Bukkit API, which should delegate safely (or buffer itself if needed) ++ return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getBiome" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return Biome.PLAINS; // Default biome on error ++ } ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + // Paper start + @Override + 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 (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 ++ } ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + // Paper end + + @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 + this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio); + } + +@@ -370,12 +543,50 @@ public class CraftBlock implements Block { + + @Override + public boolean isBlockPowered() { +- return this.world.getMinecraftWorld().getDirectSignalTo(this.position) > 0; ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "isBlockPowered"); // Strict check if applicable ++ ++ try { ++ // Requires Level.getDirectSignalTo ++ if (this.world instanceof net.minecraft.world.level.Level nmsLevel) { ++ return nmsLevel.getDirectSignalTo(this.position) > 0; ++ } ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: isBlockPowered called on non-Level - returning false"); ++ return false; ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for isBlockPowered" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return false; ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override + public boolean isBlockIndirectlyPowered() { +- return this.world.getMinecraftWorld().hasNeighborSignal(this.position); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ ++ if (needsBuffering(level, handlingMode)) { ++ // Buffered path ++ return executeBufferedRead(level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_IS_INDIRECTLY_POWERED, new Object[]{this.position}, false, "isBlockIndirectlyPowered"); ++ } else { ++ // Strict/Disabled/Non-ServerLevel path ++ checkStrictMode(level, handlingMode, "isBlockIndirectlyPowered"); ++ try { ++ // Requires Level.hasNeighborSignal ++ if (this.world instanceof net.minecraft.world.level.Level nmsLevel) { ++ return nmsLevel.hasNeighborSignal(this.position); ++ } ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: isBlockIndirectlyPowered called on non-Level - returning false"); ++ return false; ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for isBlockIndirectlyPowered" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return false; ++ } ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override +@@ -397,46 +608,102 @@ 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 ++ ++ 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; ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override + public boolean isBlockFaceIndirectlyPowered(BlockFace face) { +- int power = this.world.getMinecraftWorld().getSignal(this.position, CraftBlock.blockFaceToNotch(face)); +- +- Block relative = this.getRelative(face); +- if (relative.getType() == Material.REDSTONE_WIRE) { +- return Math.max(power, relative.getData()) > 0; ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "isBlockFaceIndirectlyPowered"); // Strict check if applicable ++ ++ try { ++ // Requires Level.getSignal and potentially relative block access (which might need safety checks) ++ if (this.world instanceof net.minecraft.world.level.Level nmsLevel) { ++ int power = nmsLevel.getSignal(this.position, CraftBlock.blockFaceToNotch(face)); ++ Block relative = this.getRelative(face); // getRelative delegates, safety depends on target block ++ if (relative.getType() == Material.REDSTONE_WIRE) { ++ // getData might need buffering if called on relative block ++ return Math.max(power, relative.getData()) > 0; ++ } ++ return power > 0; ++ } ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: isBlockFaceIndirectlyPowered called on non-Level - returning false"); ++ return false; ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for isBlockFaceIndirectlyPowered" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return false; + } +- +- return power > 0; ++ // Leaf end - SparklyPaper - parallel world ticking + } + ++ // Leaf start - SparklyPaper - parallel world ticking + @Override + public int getBlockPower(BlockFace face) { +- int power = 0; +- net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); +- int x = this.getX(); +- int y = this.getY(); +- int z = this.getZ(); +- if ((face == BlockFace.DOWN || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x, y - 1, z), Direction.DOWN)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x, y - 1, z))); +- if ((face == BlockFace.UP || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x, y + 1, z), Direction.UP)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x, y + 1, z))); +- if ((face == BlockFace.EAST || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x + 1, y, z), Direction.EAST)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x + 1, y, z))); +- if ((face == BlockFace.WEST || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x - 1, y, z), Direction.WEST)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x - 1, y, z))); +- if ((face == BlockFace.NORTH || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x, y, z - 1), Direction.NORTH)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x, y, z - 1))); +- if ((face == BlockFace.SOUTH || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x, y, z + 1), Direction.SOUTH)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x, y, z + 1))); +- return power > 0 ? power : (face == BlockFace.SELF ? this.isBlockIndirectlyPowered() : this.isBlockFaceIndirectlyPowered(face)) ? 15 : 0; +- } +- +- private static int getPower(int i, net.minecraft.world.level.block.state.BlockState iblockdata) { +- if (!iblockdata.is(Blocks.REDSTONE_WIRE)) { +- return i; ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ ++ if (needsBuffering(level, handlingMode)) { ++ // Buffered path ++ return executeBufferedRead(level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_GET_BLOCK_POWER, new Object[]{this.position, face}, 0, "getBlockPower"); + } else { +- int j = iblockdata.getValue(RedStoneWireBlock.POWER); ++ // Strict/Disabled/Non-ServerLevel path ++ checkStrictMode(level, handlingMode, "getBlockPower"); ++ try { ++ // Requires Level for hasSignal and getBlockState ++ if (this.world instanceof net.minecraft.world.level.Level nmsLevel) { ++ int power = 0; ++ int x = this.getX(); int y = this.getY(); int z = this.getZ(); ++ BlockPos currentPos = this.position; // Use immutable position ++ ++ // Check neighbors using relative positions ++ if ((face == BlockFace.DOWN || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.below(), Direction.DOWN)) power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.below())); ++ if ((face == BlockFace.UP || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.above(), Direction.UP)) power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.above())); ++ if ((face == BlockFace.EAST || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.east(), Direction.EAST)) power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.east())); ++ if ((face == BlockFace.WEST || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.west(), Direction.WEST)) power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.west())); ++ if ((face == BlockFace.NORTH || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.north(), Direction.NORTH)) power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.north())); ++ if ((face == BlockFace.SOUTH || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.south(), Direction.SOUTH)) power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.south())); ++ ++ // Need to call isBlockIndirectlyPowered/isBlockFaceIndirectlyPowered safely ++ boolean indirect = (face == BlockFace.SELF) ? this.isBlockIndirectlyPowered() : this.isBlockFaceIndirectlyPowered(face); ++ return power > 0 ? power : (indirect ? 15 : 0); ++ } else { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: getBlockPower called on non-Level - returning 0"); ++ return 0; ++ } ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getBlockPower" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return 0; ++ } ++ } ++ } + +- return j > i ? j : i; ++ // Static helper, safe ++ public static int getPower(int currentMax, net.minecraft.world.level.block.state.BlockState neighborState) { ++ if (!neighborState.is(Blocks.REDSTONE_WIRE)) { ++ return currentMax; ++ } else { ++ int neighborPower = neighborState.getValue(RedStoneWireBlock.POWER); ++ return Math.max(neighborPower, currentMax); + } + } ++ // Leaf end - SparklyPaper - parallel world ticking + + @Override + public int getBlockPower() { +@@ -479,105 +746,179 @@ public class CraftBlock implements Block { + + @Override + public PistonMoveReaction getPistonMoveReaction() { ++ // Leaf - SparklyPaper - parallel world ticking - Uses safe getNMS() + return PistonMoveReaction.getById(this.getNMS().getPistonPushReaction().ordinal()); + } + + @Override + public boolean breakNaturally() { +- return this.breakNaturally(null); ++ // Leaf start - SparklyPaper - parallel world ticking - Write operation check ++ ServerLevel level = getServerLevel(); ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ if (level != null) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, position, "PWT: Cannot modify world asynchronously (breakNaturally)"); ++ } else { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Attempted breakNaturally on non-ServerLevel - safety not guaranteed."); ++ } ++ } ++ // 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 + } + + @Override + public boolean breakNaturally(ItemStack item) { + // Paper start +- return this.breakNaturally(item, false); ++ return this.breakNaturally(item, true, true); // Leaf - SparklyPaper - parallel world ticking - Delegates to full overload + } + + @Override + public boolean breakNaturally(boolean triggerEffect, boolean dropExperience) { +- return this.breakNaturally(null, triggerEffect, dropExperience); ++ return this.breakNaturally(null, triggerEffect, dropExperience);// Leaf - SparklyPaper - parallel world ticking - Delegates to full overload + } + + @Override + public boolean breakNaturally(ItemStack item, boolean triggerEffect, boolean dropExperience) { + // Paper end + // Order matters here, need to drop before setting to air so skulls can get their data ++ // Leaf start - SparklyPaper - parallel world ticking - Write operation check ++ // (already done in simpler overload, but check again for safety) ++ ServerLevel level = getServerLevel(); ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ if (level != null) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, position, "PWT: Cannot modify world asynchronously (breakNaturally item...)"); ++ } else { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Attempted breakNaturally(item...) on non-ServerLevel - safety not guaranteed."); ++ } ++ } ++ ++ // Get NMS state safely + net.minecraft.world.level.block.state.BlockState iblockdata = this.getNMS(); + net.minecraft.world.level.block.Block block = iblockdata.getBlock(); + net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item); +- boolean result = false; +- +- // Modelled off EntityHuman#hasBlock +- if (block != Blocks.AIR && (item == null || !iblockdata.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(iblockdata))) { +- net.minecraft.world.level.block.Block.dropResources(iblockdata, this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), null, nmsItem, false); // Paper - Properly handle xp dropping +- // Paper start - improve Block#breanNaturally +- if (triggerEffect) { +- if (iblockdata.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock) { +- this.world.levelEvent(net.minecraft.world.level.block.LevelEvent.SOUND_EXTINGUISH_FIRE, this.position, 0); +- } else { +- this.world.levelEvent(net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK, this.position, net.minecraft.world.level.block.Block.getId(iblockdata)); ++ boolean droppedItems = false; ++ ++ // Experience dropping requires ServerLevel ++ ServerLevel serverLevelForDrops = getServerLevel(); // Re-get ServerLevel specifically for drop logic ++ ++ if (serverLevelForDrops != null) { // Only attempt drops/XP if we have a ServerLevel ++ // Check if block should drop items ++ if (!iblockdata.isAir() && (item == null || !iblockdata.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(iblockdata))) { ++ // Drop items using ServerLevel ++ net.minecraft.world.level.block.Block.dropResources(iblockdata, serverLevelForDrops, this.position, this.world.getBlockEntity(this.position), null, nmsItem, false); ++ ++ // Trigger effect using LevelAccessor (safe) ++ if (triggerEffect) { ++ int eventId = (iblockdata.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock) ++ ? net.minecraft.world.level.block.LevelEvent.SOUND_EXTINGUISH_FIRE ++ : net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK; ++ int eventData = (eventId == net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK) ++ ? net.minecraft.world.level.block.Block.getId(iblockdata) : 0; ++ this.world.levelEvent(eventId, this.position, eventData); + } ++ // Drop experience using ServerLevel ++ if (dropExperience) { ++ int xp = block.getExpDrop(iblockdata, serverLevelForDrops, this.position, nmsItem, true); ++ if (xp > 0) { // Only pop if there's XP to drop ++ block.popExperience(serverLevelForDrops, this.position, xp); ++ } ++ } ++ droppedItems = true; ++ } ++ } else { ++ // Log if we couldn't drop XP because it wasn't a ServerLevel ++ if (dropExperience && !iblockdata.isAir()) { // Only warn if XP was requested and block wasn't air ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Cannot drop experience for breakNaturally: Not a ServerLevel."); ++ } ++ // Still trigger effects if requested and possible with LevelAccessor ++ if (triggerEffect && !iblockdata.isAir()) { ++ int eventId = (iblockdata.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock) ++ ? net.minecraft.world.level.block.LevelEvent.SOUND_EXTINGUISH_FIRE ++ : net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK; ++ int eventData = (eventId == net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK) ++ ? net.minecraft.world.level.block.Block.getId(iblockdata) : 0; ++ this.world.levelEvent(eventId, this.position, eventData); + } +- if (dropExperience) block.popExperience(this.world.getMinecraftWorld(), this.position, block.getExpDrop(iblockdata, this.world.getMinecraftWorld(), this.position, nmsItem, true)); +- // Paper end +- result = true; + } + +- // SPIGOT-6778: Directly call setBlock instead of setTypeAndData, so that the tile entiy is not removed and custom remove logic is run. +- // Paper start - improve breakNaturally ++ // Remove the block using LevelAccessor (safe) + boolean destroyed = this.world.removeBlock(this.position, false); + if (destroyed) { ++ // Call destroy hook using LevelAccessor (safe) + block.destroy(this.world, this.position, iblockdata); + } +- if (result) { +- // special cases ++ ++ // Special case handling - requires Level, check again ++ if (droppedItems && this.world instanceof net.minecraft.world.level.Level nmsLevelForSpecialCases) { + if (block instanceof net.minecraft.world.level.block.IceBlock iceBlock) { +- iceBlock.afterDestroy(this.world.getMinecraftWorld(), this.position, nmsItem); ++ iceBlock.afterDestroy(nmsLevelForSpecialCases, this.position, nmsItem); + } else if (block instanceof net.minecraft.world.level.block.TurtleEggBlock turtleEggBlock) { +- turtleEggBlock.decreaseEggs(this.world.getMinecraftWorld(), this.position, iblockdata); ++ turtleEggBlock.decreaseEggs(nmsLevelForSpecialCases, this.position, iblockdata); + } ++ } else if (droppedItems) { // Log if special cases couldn't run ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Cannot perform post-break special actions: Not a Level."); + } +- return destroyed && result; +- // Paper end ++ ++ // Return true if the block was successfully destroyed AND items were dropped (or would have dropped if tool was right) ++ return destroyed && droppedItems; ++ // Leaf end - SparklyPaper - parallel world ticking - Write operation check + } + + @Override + public boolean applyBoneMeal(BlockFace face) { ++ // 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; +- 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 +- world.captureTreeGeneration = true; ++ level.captureTreeGeneration = true; + InteractionResult result = BoneMealItem.applyBonemeal(context); +- world.captureTreeGeneration = false; +- +- if (world.capturedBlockStates.size() > 0) { +- TreeType treeType = SaplingBlock.treeType; +- SaplingBlock.treeType = null; +- List blocks = new ArrayList<>(world.capturedBlockStates.values()); +- world.capturedBlockStates.clear(); ++ level.captureTreeGeneration = false; ++ ++ if (level.capturedBlockStates.size() > 0) { ++ TreeType treeType = SaplingBlock.getTreeTypeRT(); // Use thread-local getter ++ SaplingBlock.setTreeTypeRT(null); // Use thread-local setter ++ // Need Bukkit BlockState list for events ++ List bukkitStates = new ArrayList<>(level.capturedBlockStates.values()); ++ level.capturedBlockStates.clear(); // Clear NMS map + StructureGrowEvent structureEvent = null; + + if (treeType != null) { +- structureEvent = new StructureGrowEvent(this.getLocation(), treeType, true, null, blocks); ++ structureEvent = new StructureGrowEvent(this.getLocation(), treeType, true, null, bukkitStates); + Bukkit.getPluginManager().callEvent(structureEvent); + } + +- event = new BlockFertilizeEvent(CraftBlock.at(world, this.getPosition()), null, blocks); ++ event = new BlockFertilizeEvent(this, null, bukkitStates); // Use 'this' as the CraftBlock + event.setCancelled(structureEvent != null && structureEvent.isCancelled()); + Bukkit.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { +- for (BlockState blockstate : blocks) { +- blockstate.update(true); +- world.checkCapturedTreeStateForObserverNotify(this.position, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); // Paper - notify observers even if grow failed ++ for (org.bukkit.block.BlockState blockstate : bukkitStates) { ++ blockstate.update(true); // This performs the actual block changes ++ // Notify observers using the captured state info ++ level.checkCapturedTreeStateForObserverNotify(this.position, (org.bukkit.craftbukkit.block.CraftBlockState) blockstate); + } + } + } + ++ // Return true only if bonemeal succeeded AND the event wasn't cancelled + return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled()); ++ // Leaf end - SparklyPaper - parallel world ticking - Write operation check + } + + @Override +@@ -592,31 +933,70 @@ public class CraftBlock implements Block { + + @Override + public Collection getDrops(ItemStack item, Entity entity) { +- net.minecraft.world.level.block.state.BlockState iblockdata = this.getNMS(); +- net.minecraft.world.item.ItemStack nms = CraftItemStack.asNMSCopy(item); ++ // Leaf start - SparklyPaper - parallel world ticking - Write operation check ++ // Requires ServerLevel for Block.getDrops call ++ ServerLevel level = getServerLevel(); ++ if (level == null) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Cannot getDrops: Not a ServerLevel."); ++ return Collections.emptyList(); ++ } + +- // Modelled off EntityHuman#hasBlock +- if (item == null || CraftBlockData.isPreferredTool(iblockdata, nms)) { +- return net.minecraft.world.level.block.Block.getDrops(iblockdata, (ServerLevel) this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), entity == null ? null : ((CraftEntity) entity).getHandle(), nms) +- .stream().map(CraftItemStack::asBukkitCopy).collect(Collectors.toList()); +- } else { ++ // Strict check if applicable ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "getDrops"); ++ ++ try { ++ net.minecraft.world.level.block.state.BlockState iblockdata = this.getNMS(); // Use safe getNMS ++ net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item); ++ net.minecraft.world.entity.Entity nmsEntity = (entity == null) ? null : ((CraftEntity) entity).getHandle(); ++ ++ // Check tool requirement ++ if (item == null || !iblockdata.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(iblockdata)) { ++ // Call NMS getDrops using the verified ServerLevel ++ List nmsDrops = net.minecraft.world.level.block.Block.getDrops( ++ iblockdata, level, this.position, this.world.getBlockEntity(this.position), nmsEntity, nmsItem ++ ); ++ // Convert to Bukkit ItemStacks ++ return nmsDrops.stream().map(CraftItemStack::asBukkitCopy).collect(Collectors.toList()); ++ } else { ++ // Tool was required but not correct/present ++ return Collections.emptyList(); ++ } ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getDrops", e); + return Collections.emptyList(); + } ++ // Leaf end - SparklyPaper - parallel world ticking - Write operation check + } + + @Override + public boolean isPreferredTool(ItemStack item) { ++ // Leaf - SparklyPaper - parallel world ticking - Uses safe getNMS() + net.minecraft.world.level.block.state.BlockState iblockdata = this.getNMS(); +- net.minecraft.world.item.ItemStack nms = CraftItemStack.asNMSCopy(item); +- return CraftBlockData.isPreferredTool(iblockdata, nms); ++ net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item); ++ return iblockdata.requiresCorrectToolForDrops() && nmsItem.isCorrectToolForDrops(iblockdata); // Leaf - SparklyPaper - parallel world ticking - Delegate to helper which checks tool tags + } + + @Override + public float getBreakSpeed(Player player) { + Preconditions.checkArgument(player != null, "player cannot be null"); +- return this.getNMS().getDestroyProgress(((CraftPlayer) player).getHandle(), this.world, this.position); ++ // Leaf start - SparklyPaper - parallel world ticking ++ // Requires LevelReader (LevelAccessor is sufficient) ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "getBreakSpeed"); // Strict check if applicable ++ ++ try { ++ // Uses safe getNMS() ++ return this.getNMS().getDestroyProgress(((CraftPlayer) player).getHandle(), this.world, this.position); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getBreakSpeed" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return 0.0f; ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + ++ // Leaf - SparklyPaper - parallel world ticking - Metadata methods delegate to CraftWorld, assumed safe + @Override + public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { + this.getCraftWorld().getBlockMetadata().setMetadata(this, metadataKey, newMetadataValue); +@@ -639,57 +1019,147 @@ public class CraftBlock implements Block { + + @Override + public boolean isPassable() { +- return this.getNMS().getCollisionShape(this.world, this.position).isEmpty(); ++ // Leaf start - SparklyPaper - parallel world ticking ++ // Requires LevelReader (LevelAccessor is sufficient) ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "isPassable"); // Strict check if applicable ++ ++ try { ++ // Uses safe getNMS() ++ VoxelShape shape = this.getNMS().getCollisionShape(this.world, this.position); ++ return shape.isEmpty(); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for isPassable" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return true; // Default to passable on error? Or false? Passable seems safer. ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override + public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { ++ // 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(); + + Preconditions.checkArgument(direction != null, "Vector direction cannot be null"); + direction.checkFinite(); +- Preconditions.checkArgument(direction.lengthSquared() > 0, "Direction's magnitude (%s) must be greater than 0", direction.lengthSquared()); ++ Preconditions.checkArgument(direction.lengthSquared() > 1.0E-8, "Direction's magnitude (%s) must be greater than 0", direction.lengthSquared()); // Avoid near-zero vectors + + Preconditions.checkArgument(fluidCollisionMode != null, "FluidCollisionMode cannot be null"); +- if (maxDistance < 0.0D) { +- return null; +- } +- +- Vector dir = direction.clone().normalize().multiply(maxDistance); +- Vec3 startPos = CraftLocation.toVec3D(start); +- Vec3 endPos = startPos.add(dir.getX(), dir.getY(), dir.getZ()); ++ if (maxDistance < 0.0D) return null; // Max distance must be non-negative ++ ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ ++ if (needsBuffering(level, handlingMode)) { ++ // Buffered path ++ HitResult nmsHitResult = executeBufferedRead( ++ level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_RAY_TRACE, ++ new Object[]{this.position, start, direction, maxDistance, fluidCollisionMode}, // Pass all params ++ null, "rayTrace" ++ ); ++ // Convert NMS result (can be null) to Bukkit result ++ return CraftRayTraceResult.fromNMS(this.getWorld(), nmsHitResult); ++ } else { ++ // Strict/Disabled/Non-ServerLevel path ++ checkStrictMode(level, handlingMode, "rayTrace"); ++ try { ++ // Calculate start and end points for the ray trace ++ Vec3 startPos = CraftLocation.toVec3D(start); ++ Vector dirNormalized = direction.clone().normalize(); // Normalize once ++ Vec3 endPos = startPos.add(dirNormalized.getX() * maxDistance, dirNormalized.getY() * maxDistance, dirNormalized.getZ() * maxDistance); ++ ++ // Perform the clip using LevelAccessor (safe) ++ // Pass the block's position as the context position for the clip function ++ HitResult nmsHitResult = this.world.clip( ++ new ClipContext(startPos, endPos, ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toNMS(fluidCollisionMode), CollisionContext.empty()), ++ this.position // Provide the block's position here ++ ); + +- HitResult nmsHitResult = this.world.clip(new ClipContext(startPos, endPos, ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toNMS(fluidCollisionMode), CollisionContext.empty()), this.position); +- return CraftRayTraceResult.fromNMS(this.getWorld(), nmsHitResult); ++ // Convert NMS result to Bukkit result ++ return CraftRayTraceResult.fromNMS(this.getWorld(), nmsHitResult); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for rayTrace" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return null; // Return null on error ++ } ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override + public BoundingBox getBoundingBox() { +- VoxelShape shape = this.getNMS().getShape(this.world, this.position); +- +- if (shape.isEmpty()) { +- return new BoundingBox(); // Return an empty bounding box if the block has no dimension ++ // Leaf start - SparklyPaper - parallel world ticking ++ // Requires LevelReader (LevelAccessor is sufficient) ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "getBoundingBox"); // Strict check if applicable ++ ++ try { ++ // Uses safe getNMS() ++ VoxelShape shape = this.getNMS().getShape(this.world, this.position); ++ if (shape.isEmpty()) { ++ // Return a zero-sized box at the block's corner if shape is empty ++ return new BoundingBox(getX(), getY(), getZ(), getX(), getY(), getZ()); ++ } ++ // Get AABB relative to 0,0,0 and offset by block position ++ AABB aabb = shape.bounds(); ++ return new BoundingBox( ++ this.getX() + aabb.minX, this.getY() + aabb.minY, this.getZ() + aabb.minZ, ++ this.getX() + aabb.maxX, this.getY() + aabb.maxY, this.getZ() + aabb.maxZ ++ ); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getBoundingBox" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ // Default to a full 1x1x1 box on error ++ return new BoundingBox(getX(), getY(), getZ(), getX() + 1, getY() + 1, getZ() + 1); + } +- +- AABB aabb = shape.bounds(); +- return new BoundingBox(this.getX() + aabb.minX, this.getY() + aabb.minY, this.getZ() + aabb.minZ, this.getX() + aabb.maxX, this.getY() + aabb.maxY, this.getZ() + aabb.maxZ); ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override + public org.bukkit.util.VoxelShape getCollisionShape() { +- VoxelShape shape = this.getNMS().getCollisionShape(this.world, this.position); +- return new CraftVoxelShape(shape); ++ // Leaf start - SparklyPaper - parallel world ticking ++ // Requires LevelReader (LevelAccessor is sufficient) ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; ++ checkStrictMode(level, handlingMode, "getCollisionShape"); // Strict check if applicable ++ ++ try { ++ // Uses safe getNMS() ++ VoxelShape shape = this.getNMS().getCollisionShape(this.world, this.position); ++ return new CraftVoxelShape(shape); // Wrap NMS shape ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getCollisionShape" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return new CraftVoxelShape(net.minecraft.world.phys.shapes.Shapes.empty()); // Default to empty shape on error ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override + public boolean canPlace(BlockData data) { + Preconditions.checkArgument(data != null, "BlockData cannot be null"); +- net.minecraft.world.level.block.state.BlockState iblockdata = ((CraftBlockData) data).getState(); +- net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); ++ // Leaf start - SparklyPaper - parallel world ticking ++ ServerLevel level = getServerLevel(); ++ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling; + +- return iblockdata.canSurvive(world, this.position); ++ if (needsBuffering(level, handlingMode)) { ++ // Buffered path ++ return executeBufferedRead(level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_CAN_PLACE, new Object[]{this.position, data}, false, "canPlace"); ++ } else { ++ // Strict/Disabled/Non-ServerLevel path ++ checkStrictMode(level, handlingMode, "canPlace"); ++ try { ++ net.minecraft.world.level.block.state.BlockState nmsData = ((CraftBlockData) data).getState(); ++ // LevelAccessor has canSurvive ++ return nmsData.canSurvive(this.world, this.position); ++ } catch (Exception e) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for canPlace" + (level == null ? " (Not a ServerLevel)" : ""), e); ++ return false; // Default to false on error ++ } ++ } ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override +@@ -700,7 +1170,10 @@ public class CraftBlock implements Block { + // Paper start + @Override + public com.destroystokyo.paper.block.BlockSoundGroup getSoundGroup() { +- return new com.destroystokyo.paper.block.CraftBlockSoundGroup(getNMS().getBlock().defaultBlockState().getSoundType()); ++ // Leaf start - SparklyPaper - parallel world ticking ++ net.minecraft.world.level.block.SoundType nmsSoundType = this.getNMS().getSoundType(); ++ return new com.destroystokyo.paper.block.CraftBlockSoundGroup(nmsSoundType); ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override +@@ -713,26 +1186,76 @@ public class CraftBlock implements Block { + return this.getNMS().getBlock().getDescriptionId(); + } + ++ // Leaf start - SparklyPaper - parallel world ticking ++ @Override + public boolean isValidTool(ItemStack itemStack) { +- return getDrops(itemStack).size() != 0; ++ if (itemStack == null || itemStack.getType().isAir()) { ++ return false; ++ } ++ return !this.getDrops(itemStack).isEmpty(); ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override + public void tick() { +- final ServerLevel level = this.world.getMinecraftWorld(); ++ // Leaf start - SparklyPaper - parallel world ticking - Write operation check ++ // (block ticks can modify state) ++ ServerLevel level = getServerLevel(); ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ if (level != null) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, position, "PWT: Cannot tick block asynchronously (tick)"); ++ } else { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Attempted tick on non-ServerLevel - safety not guaranteed."); ++ return; // Cannot tick without ServerLevel ++ } ++ } else if (level == null) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Cannot tick block: Not a ServerLevel."); ++ return; ++ } ++ + this.getNMS().tick(level, this.position, level.random); ++ // Leaf end - SparklyPaper - parallel world ticking - + } + + + @Override + public void fluidTick() { +- this.getNMSFluid().tick(this.world.getMinecraftWorld(), this.position, this.getNMS()); ++ // Leaf start - SparklyPaper - parallel world ticking ++ // Fluid ticks can modify state ++ ServerLevel level = getServerLevel(); ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ if (level != null) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, position, "PWT: Cannot tick fluid asynchronously (fluidTick)"); ++ } else { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Attempted fluidTick on non-ServerLevel - safety not guaranteed."); ++ return; // Cannot tick fluid without ServerLevel ++ } ++ } else if (level == null) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Cannot tick fluid: Not a ServerLevel."); ++ return; ++ } ++ this.getNMSFluid().tick(level, this.position, this.getNMS()); ++ // Leaf end - SparklyPaper - parallel world ticking + } + + @Override + public void randomTick() { +- final ServerLevel level = this.world.getMinecraftWorld(); ++ // Leaf start - SparklyPaper - parallel world ticking - Write operation check ++ // (random ticks can modify state) ++ ServerLevel level = getServerLevel(); ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) { ++ if (level != null) { ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, position, "PWT: Cannot randomTick block asynchronously"); ++ } else { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Attempted randomTick on non-ServerLevel - safety not guaranteed."); ++ return; ++ } ++ } else if (level == null) { ++ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Cannot randomTick block: Not a ServerLevel."); ++ return; ++ } + this.getNMS().randomTick(level, this.position, level.random); ++ // Leaf end - SparklyPaper - parallel world ticking - Write operation check + } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +index 768d3f93da2522d467183654260a8bd8653588b1..5cef786fa2e5dfd3e7b79918bc73af02891b0bea 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +@@ -26,6 +26,27 @@ 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) ++ ++ // Leaf start - SparklyPaper - parallel world ticking mod ++ // 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) { ++ 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) ++ DISABLE_SNAPSHOT_TL.set(value); ++ synchronized (CraftBlockEntityState.class) { ++ DISABLE_SNAPSHOT = value; ++ } ++ } ++ // Leaf end - SparklyPaper - parallel world ticking mod + + public CraftBlockEntityState(World world, T tileEntity) { + super(world, tileEntity.getBlockPos(), tileEntity.getBlockState()); +@@ -34,8 +55,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.snapshot = this.tileEntity; + } else { + this.snapshot = this.createSnapshot(tileEntity); +diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +index fa63a6cfcfcc4eee4503a82d85333c139c8c8b2b..d106f65e4b745242484a195958fc559268a7dee0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +@@ -215,6 +215,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"); ++ } ++ // SparklyPaper end - parallel world ticking ++ + if (block.getType() != this.getType()) { + if (!force) { + return false; +@@ -350,6 +356,8 @@ 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 + 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 55572e799b5c8a74a546ac8febc14f80d5731c52..08a06c23c831a4de45b3e537228b837911019da8 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +@@ -249,8 +249,8 @@ public final class CraftBlockStates { + net.minecraft.world.level.block.state.BlockState blockData = craftBlock.getNMS(); + BlockEntity tileEntity = craftBlock.getHandle().getBlockEntity(blockPosition); + // Paper start - block state snapshots +- 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) + try { + // Paper end + CraftBlockState blockState = CraftBlockStates.getBlockState(world, blockPosition, blockData, tileEntity); +@@ -258,7 +258,7 @@ public final class CraftBlockStates { + return blockState; + // Paper start + } finally { +- CraftBlockEntityState.DISABLE_SNAPSHOT = prev; ++ CraftBlockEntityState.setDisableSnapshotTL(prev); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) + } + // Paper end + } +diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +index c2552c3706831f7012b5b449fa43c7d5990056a4..4e8a1d01a6c0afef92ae56cc4909af06d63e5268 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -961,6 +961,28 @@ 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 BlockPosition 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) ++ ++ // Leaf start - SparklyPaper - parallel world ticking mod ++ // 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) { ++ 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) { ++ sourceBlockOverride = value; ++ } ++ } ++ // Leaf end - SparklyPaper - parallel world ticking mod + + public static boolean handleBlockSpreadEvent(LevelAccessor world, BlockPos source, BlockPos target, net.minecraft.world.level.block.state.BlockState block, int flag) { + // Suppress during worldgen +@@ -972,7 +994,10 @@ public class CraftEventFactory { + CraftBlockState state = CraftBlockStates.getBlockState(world, target, flag); + state.setData(block); + +- BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverride != null ? CraftEventFactory.sourceBlockOverride : source), state); ++ // Leaf start - SparklyPaper parallel world ticking mod (collapse original behavior) ++ final BlockPos sourceBlockOverrideRTSnap = getSourceBlockOverrideRT(); ++ BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, sourceBlockOverrideRTSnap != null ? sourceBlockOverrideRTSnap : source), state); // SparklyPaper - parallel world ticking ++ // Leaf end - SparklyPaper parallel world ticking mod (collapse original behavior) + Bukkit.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { +@@ -2265,7 +2290,7 @@ public class CraftEventFactory { + CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemStack.copyWithCount(1)); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), CraftVector.toBukkit(to)); +- if (!net.minecraft.world.level.block.DispenserBlock.eventFired) { ++ if (!net.minecraft.world.level.block.DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) + if (!event.callEvent()) { + return itemStack; + } +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java +index e4e2e42d0ca25df7fe9f2dd4275610e45fcb2c84..e7c6b2ab5f2c68f3319ccd52785c8d3488a2eef7 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java +@@ -19,11 +19,39 @@ class CraftAsyncTask extends CraftTask { + + @Override + public boolean isSync() { ++ // Leaf start - Parallel world ticking ++ // Return true if we should run this task synchronously when parallel world ticking is enabled and runAsyncTasksSync is true ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && ++ org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.runAsyncTasksSync) { ++ return true; ++ } ++ // Leaf end - Parallel world ticking + return false; + } + + @Override + public void run() { ++ // Leaf start - Parallel world ticking ++ // If parallel world ticking is enabled and we're configured to run async tasks sync, ++ // execute the task as if it were a sync task (directly on the main thread) ++ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && ++ org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.runAsyncTasksSync) { ++ try { ++ super.run(); ++ } catch (final Throwable t) { ++ this.getOwner().getLogger().log( ++ Level.WARNING, ++ String.format( ++ "Plugin %s generated an exception while executing task %s (forced sync mode)", ++ this.getOwner().getDescription().getFullName(), ++ this.getTaskId()), ++ t); ++ } ++ return; ++ } ++ // Leaf end - Parallel world ticking ++ ++ // Original async implementation + final Thread thread = Thread.currentThread(); + // Paper start - name threads according to running plugin + final String nameBefore = thread.getName(); diff --git a/leaf-archived-patches/unapplied/server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch b/leaf-archived-patches/unapplied/server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch deleted file mode 100644 index 2defa582..00000000 --- a/leaf-archived-patches/unapplied/server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch +++ /dev/null @@ -1,703 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Altiami -Date: Wed, 5 Mar 2025 13:16:44 -0800 -Subject: [PATCH] SparklyPaper: Parallel world ticking - -Original project: https://github.com/SparklyPower/SparklyPaper - -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 a4aa2615823d77920ff55b8aa0bcc27a54b8c3e1..2fb65ce228da94eb7d9364ee0f94582300178f1d 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 String getThreadContext() { - return "thread=" + Thread.currentThread().getName(); -@@ -26,14 +27,14 @@ public class TickThread extends Thread { - public static void ensureTickThread(final String reason) { - if (!isTickThread()) { - LOGGER.error("Thread failed main thread check: " + reason + ", context=" + getThreadContext(), new Throwable()); -- throw new IllegalStateException(reason); -+ if (HARD_THROW) throw new IllegalStateException(reason); // SparklyPaper - parallel world ticking - } - } - - public static void ensureTickThread(final Level world, final BlockPos pos, final String reason) { - 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()); - throw new IllegalStateException(ex); - } -@@ -42,7 +43,7 @@ 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()); - throw new IllegalStateException(ex); - } -@@ -51,7 +52,7 @@ 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()); - throw new IllegalStateException(ex); - } -@@ -60,7 +61,7 @@ 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()); - throw new IllegalStateException(ex); - } -@@ -69,7 +70,7 @@ 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()); - throw new IllegalStateException(ex); - } -@@ -78,7 +79,7 @@ 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()); - throw new IllegalStateException(ex); - } -@@ -87,12 +88,70 @@ 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()); - throw new IllegalStateException(ex); - } - } - -+ // SparklyPaper start - 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 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); -+ } -+ } -+ -+ // 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); -+ } -+ } -+ -+ // 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); -+ } -+ } -+ -+ public static String getTickThreadInformation(net.minecraft.server.MinecraftServer minecraftServer) { -+ StringBuilder sb = new StringBuilder(); -+ Thread currentThread = Thread.currentThread(); -+ sb.append("Is tick thread? "); -+ sb.append(currentThread instanceof TickThread); -+ sb.append("; Is server level tick thread? "); -+ 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("; Is iterating over levels? "); -+ sb.append(minecraftServer.isIteratingOverLevels); -+ sb.append("; Are we going to hard throw? "); -+ sb.append(HARD_THROW); -+ return sb.toString(); -+ } -+ -+ public static boolean isServerLevelTickThread() { -+ return Thread.currentThread() instanceof ServerLevelTickThread; -+ } -+ // SparklyPaper end - 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(); -@@ -133,46 +192,74 @@ 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) - } - - 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) - } - - 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) - } - - 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) - } - - 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) - } - - 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) - } - - 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) - } - - 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) - } - - 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) - } - - 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) -+ } -+ -+ // SparklyPaper start - 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(); -+ } - } - - 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 ServerLevelTickThread(String name) { -+ super(name); -+ } -+ -+ public ServerLevelTickThread(Runnable run, String name) { -+ super(run, name); -+ } -+ -+ public net.minecraft.server.level.ServerLevel currentlyTickingServerLevel; - } -+ // SparklyPaper end - parallel world ticking - } -diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -index 548fcd9646dee0c40b6ba9b3dafb9ca157dfe324..d7af94890bfccd6ff665d920cecfa1e5be626aa4 100644 ---- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -+++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java -@@ -40,6 +40,12 @@ class PaperEventManager { - if (listeners.length == 0) return; - // Leaf end - Skip event if no listeners - if (event.isAsynchronous() && this.server.isPrimaryThread()) { -+ // Leaf start - Parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.runAsyncTasksSync) { -+ org.dreeam.leaf.async.world.PWTEventScheduler.getScheduler().scheduleTask(event::callEvent); -+ return; -+ } -+ // Leaf end - Parallel world ticking - throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously."); - } else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) { - // Leaf start - Multithreaded tracker -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index af33cab59932f4ec135caf94dc5828930833daf6..92463ddc6fdcf542ce4a6d2a5059d4a9f7a6085a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -455,7 +455,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - } - - 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 -+ org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot -+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) - if (!this.isChunkLoaded(x, z)) { - return true; - } -@@ -472,6 +477,8 @@ 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) - ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); - if (playerChunk == null) return false; - -@@ -522,7 +529,12 @@ 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 -+ org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot -+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable) - 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 - -@@ -750,6 +762,8 @@ 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) - this.world.captureTreeGeneration = true; - this.world.captureBlockStates = true; - boolean grownTree = this.generateTree(loc, type); -@@ -865,6 +879,8 @@ 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) - net.minecraft.world.level.Level.ExplosionInteraction explosionType; - if (!breakBlocks) { - explosionType = net.minecraft.world.level.Level.ExplosionInteraction.NONE; // Don't break blocks -@@ -956,6 +972,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x >> 4, z >> 4, "Cannot retrieve chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) - warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper - // Transient load for this tick - return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z); -@@ -986,6 +1004,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { - @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 (this.world.hasChunkAt(pos)) { - net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkAt(pos); - -@@ -2328,6 +2348,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @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) - 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 811823a1a7e24a19a7e37eb4c08efdfa19e839ed..b94efcab12d41fa8745e5bb55cfa6481d8262e74 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -@@ -75,6 +75,11 @@ public class CraftBlock implements Block { - } - - public net.minecraft.world.level.block.state.BlockState getNMS() { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); -+ } -+ // SparklyPaper end - parallel world ticking - return this.world.getBlockState(this.position); - } - -@@ -157,6 +162,11 @@ public class CraftBlock implements Block { - } - - private void setData(final byte data, int flag) { -+ // 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 - this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flag); - } - -@@ -198,6 +208,12 @@ public class CraftBlock implements Block { - } - - public static boolean setTypeAndData(LevelAccessor world, BlockPos position, net.minecraft.world.level.block.state.BlockState old, net.minecraft.world.level.block.state.BlockState blockData, boolean applyPhysics) { -+ // 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 -+ - // SPIGOT-611: need to do this to prevent glitchiness. Easier to handle this here (like /setblock) than to fix weirdness in tile entity cleanup - if (old.hasBlockEntity() && blockData.getBlock() != old.getBlock()) { // SPIGOT-3725 remove old tile entity if block changes - // SPIGOT-4612: faster - just clear tile -@@ -343,18 +359,33 @@ public class CraftBlock implements Block { - - @Override - public Biome getBiome() { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); -+ } -+ // SparklyPaper end - parallel world ticking - return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); - } - - // Paper start - @Override - public Biome getComputedBiome() { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); -+ } -+ // SparklyPaper end - parallel world ticking - return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ()); - } - // Paper end - - @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 - this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio); - } - -@@ -375,6 +406,11 @@ public class CraftBlock implements Block { - - @Override - public boolean isBlockIndirectlyPowered() { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); -+ } -+ // SparklyPaper end - parallel world ticking - return this.world.getMinecraftWorld().hasNeighborSignal(this.position); - } - -@@ -414,6 +450,11 @@ public class CraftBlock implements Block { - - @Override - public int getBlockPower(BlockFace face) { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); -+ } -+ // SparklyPaper end - parallel world ticking - int power = 0; - net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); - int x = this.getX(); -@@ -484,6 +525,11 @@ public class CraftBlock implements Block { - - @Override - public boolean breakNaturally() { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); -+ } -+ // SparklyPaper end - parallel world ticking - return this.breakNaturally(null); - } - -@@ -543,6 +589,11 @@ public class CraftBlock implements Block { - - @Override - public boolean applyBoneMeal(BlockFace face) { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); -+ } -+ // SparklyPaper end - parallel world ticking - Direction direction = CraftBlock.blockFaceToNotch(face); - BlockFertilizeEvent event = null; - ServerLevel world = this.getCraftWorld().getHandle(); -@@ -554,8 +605,8 @@ public class CraftBlock implements Block { - world.captureTreeGeneration = false; - - if (world.capturedBlockStates.size() > 0) { -- TreeType treeType = SaplingBlock.treeType; -- SaplingBlock.treeType = null; -+ TreeType treeType = SaplingBlock.getTreeTypeRT(); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) -+ SaplingBlock.setTreeTypeRT(null); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) - List blocks = new ArrayList<>(world.capturedBlockStates.values()); - world.capturedBlockStates.clear(); - StructureGrowEvent structureEvent = null; -@@ -644,6 +695,11 @@ public class CraftBlock implements Block { - - @Override - public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); -+ } -+ // SparklyPaper end - parallel world ticking - Preconditions.checkArgument(start != null, "Location start cannot be null"); - Preconditions.checkArgument(this.getWorld().equals(start.getWorld()), "Location start cannot be a different world"); - start.checkFinite(); -@@ -685,6 +741,11 @@ public class CraftBlock implements Block { - - @Override - public boolean canPlace(BlockData data) { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); -+ } -+ // SparklyPaper end - parallel world ticking - Preconditions.checkArgument(data != null, "BlockData cannot be null"); - net.minecraft.world.level.block.state.BlockState iblockdata = ((CraftBlockData) data).getState(); - net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); -@@ -719,6 +780,11 @@ public class CraftBlock implements Block { - - @Override - public void tick() { -+ // SparklyPaper start - parallel world ticking -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable) -+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); -+ } -+ // SparklyPaper end - parallel world ticking - final ServerLevel level = this.world.getMinecraftWorld(); - this.getNMS().tick(level, this.position, level.random); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -index 768d3f93da2522d467183654260a8bd8653588b1..5cef786fa2e5dfd3e7b79918bc73af02891b0bea 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java -@@ -26,6 +26,27 @@ 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) -+ -+ // Leaf start - SparklyPaper - parallel world ticking mod -+ // 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) { -+ 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) -+ DISABLE_SNAPSHOT_TL.set(value); -+ synchronized (CraftBlockEntityState.class) { -+ DISABLE_SNAPSHOT = value; -+ } -+ } -+ // Leaf end - SparklyPaper - parallel world ticking mod - - public CraftBlockEntityState(World world, T tileEntity) { - super(world, tileEntity.getBlockPos(), tileEntity.getBlockState()); -@@ -34,8 +55,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.snapshot = this.tileEntity; - } else { - this.snapshot = this.createSnapshot(tileEntity); -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -index fa63a6cfcfcc4eee4503a82d85333c139c8c8b2b..d106f65e4b745242484a195958fc559268a7dee0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java -@@ -215,6 +215,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"); -+ } -+ // SparklyPaper end - parallel world ticking -+ - if (block.getType() != this.getType()) { - if (!force) { - return false; -@@ -350,6 +356,8 @@ 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 - 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 55572e799b5c8a74a546ac8febc14f80d5731c52..08a06c23c831a4de45b3e537228b837911019da8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java -@@ -249,8 +249,8 @@ public final class CraftBlockStates { - net.minecraft.world.level.block.state.BlockState blockData = craftBlock.getNMS(); - BlockEntity tileEntity = craftBlock.getHandle().getBlockEntity(blockPosition); - // Paper start - block state snapshots -- 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) - try { - // Paper end - CraftBlockState blockState = CraftBlockStates.getBlockState(world, blockPosition, blockData, tileEntity); -@@ -258,7 +258,7 @@ public final class CraftBlockStates { - return blockState; - // Paper start - } finally { -- CraftBlockEntityState.DISABLE_SNAPSHOT = prev; -+ CraftBlockEntityState.setDisableSnapshotTL(prev); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) - } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index c2552c3706831f7012b5b449fa43c7d5990056a4..4e8a1d01a6c0afef92ae56cc4909af06d63e5268 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -961,6 +961,28 @@ 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 BlockPosition 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) -+ -+ // Leaf start - SparklyPaper - parallel world ticking mod -+ // 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) { -+ 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) { -+ sourceBlockOverride = value; -+ } -+ } -+ // Leaf end - SparklyPaper - parallel world ticking mod - - public static boolean handleBlockSpreadEvent(LevelAccessor world, BlockPos source, BlockPos target, net.minecraft.world.level.block.state.BlockState block, int flag) { - // Suppress during worldgen -@@ -972,7 +994,10 @@ public class CraftEventFactory { - CraftBlockState state = CraftBlockStates.getBlockState(world, target, flag); - state.setData(block); - -- BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverride != null ? CraftEventFactory.sourceBlockOverride : source), state); -+ // Leaf start - SparklyPaper parallel world ticking mod (collapse original behavior) -+ final BlockPos sourceBlockOverrideRTSnap = getSourceBlockOverrideRT(); -+ BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, sourceBlockOverrideRTSnap != null ? sourceBlockOverrideRTSnap : source), state); // SparklyPaper - parallel world ticking -+ // Leaf end - SparklyPaper parallel world ticking mod (collapse original behavior) - Bukkit.getPluginManager().callEvent(event); - - if (!event.isCancelled()) { -@@ -2265,7 +2290,7 @@ public class CraftEventFactory { - CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemStack.copyWithCount(1)); - - org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), CraftVector.toBukkit(to)); -- if (!net.minecraft.world.level.block.DispenserBlock.eventFired) { -+ if (!net.minecraft.world.level.block.DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior) - if (!event.callEvent()) { - return itemStack; - } -diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java -index e4e2e42d0ca25df7fe9f2dd4275610e45fcb2c84..e7c6b2ab5f2c68f3319ccd52785c8d3488a2eef7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java -+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java -@@ -19,11 +19,39 @@ class CraftAsyncTask extends CraftTask { - - @Override - public boolean isSync() { -+ // Leaf start - Parallel world ticking -+ // Return true if we should run this task synchronously when parallel world ticking is enabled and runAsyncTasksSync is true -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && -+ org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.runAsyncTasksSync) { -+ return true; -+ } -+ // Leaf end - Parallel world ticking - return false; - } - - @Override - public void run() { -+ // Leaf start - Parallel world ticking -+ // If parallel world ticking is enabled and we're configured to run async tasks sync, -+ // execute the task as if it were a sync task (directly on the main thread) -+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && -+ org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.runAsyncTasksSync) { -+ try { -+ super.run(); -+ } catch (final Throwable t) { -+ this.getOwner().getLogger().log( -+ Level.WARNING, -+ String.format( -+ "Plugin %s generated an exception while executing task %s (forced sync mode)", -+ this.getOwner().getDescription().getFullName(), -+ this.getTaskId()), -+ t); -+ } -+ return; -+ } -+ // Leaf end - Parallel world ticking -+ -+ // Original async implementation - final Thread thread = Thread.currentThread(); - // Paper start - name threads according to running plugin - final String nameBefore = thread.getName(); diff --git a/leaf-archived-patches/unapplied/src/main/java/org/dreeam/leaf/async/world/ReadOperationType.java b/leaf-archived-patches/unapplied/src/main/java/org/dreeam/leaf/async/world/ReadOperationType.java new file mode 100644 index 00000000..dffe7ba0 --- /dev/null +++ b/leaf-archived-patches/unapplied/src/main/java/org/dreeam/leaf/async/world/ReadOperationType.java @@ -0,0 +1,11 @@ +package org.dreeam.leaf.async.world; + +public enum ReadOperationType { + BLOCK_GET_BIOME, + BLOCK_GET_COMPUTED_BIOME, + BLOCK_IS_INDIRECTLY_POWERED, + BLOCK_GET_BLOCK_POWER, + BLOCK_RAY_TRACE, + BLOCK_CAN_PLACE, + BLOCK_GET_NMS_STATE +} diff --git a/leaf-archived-patches/unapplied/src/main/java/org/dreeam/leaf/async/world/WorldReadRequest.java b/leaf-archived-patches/unapplied/src/main/java/org/dreeam/leaf/async/world/WorldReadRequest.java new file mode 100644 index 00000000..8fb45b7c --- /dev/null +++ b/leaf-archived-patches/unapplied/src/main/java/org/dreeam/leaf/async/world/WorldReadRequest.java @@ -0,0 +1,10 @@ +package org.dreeam.leaf.async.world; + +import java.util.concurrent.CompletableFuture; + +public record WorldReadRequest( + ReadOperationType type, + Object[] params, // Parameters for the read operation + CompletableFuture future // Future to complete with the result +) { +} diff --git a/leaf-archived-patches/unapplied/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java b/leaf-archived-patches/unapplied/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java index 6a307db7..1aea4693 100644 --- a/leaf-archived-patches/unapplied/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java +++ b/leaf-archived-patches/unapplied/src/main/java/org/dreeam/leaf/config/modules/network/ProtocolSupport.java @@ -2,6 +2,8 @@ package org.dreeam.leaf.config.modules.network; import org.dreeam.leaf.config.ConfigModules; import org.dreeam.leaf.config.EnumConfigCategory; +import org.dreeam.leaf.protocol.DoABarrelRollPackets; +import org.dreeam.leaf.protocol.DoABarrelRollProtocol; import java.util.concurrent.ThreadLocalRandom; diff --git a/leaf-archived-patches/unapplied/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java b/leaf-archived-patches/unapplied/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java index 72284716..391a9871 100644 --- a/leaf-archived-patches/unapplied/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java +++ b/leaf-archived-patches/unapplied/src/main/java/org/stupidcraft/linearpaper/region/LinearRegionFile.java @@ -33,6 +33,8 @@ import net.minecraft.world.level.ChunkPos; import org.dreeam.leaf.config.modules.misc.RegionFormatConfig; import org.slf4j.Logger; +import net.minecraft.server.MinecraftServer; + public class LinearRegionFile implements IRegionFile { private static final long SUPERBLOCK = -4323716122432332390L; @@ -148,6 +150,7 @@ public class LinearRegionFile implements IRegionFile { // Save only once on shutdown if (!closed) return; } + long timestamp = getTimestamp(); short chunkCount = 0; diff --git a/leaf-archived-patches/work/server/0024-Rail-Optimization-optimized-PoweredRailBlock-logic.patch b/leaf-archived-patches/work/server/0024-Rail-Optimization-optimized-PoweredRailBlock-logic.patch deleted file mode 100644 index e0f434ec..00000000 --- a/leaf-archived-patches/work/server/0024-Rail-Optimization-optimized-PoweredRailBlock-logic.patch +++ /dev/null @@ -1,405 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> -Date: Sat, 17 Feb 2024 17:57:08 -0500 -Subject: [PATCH] Rail Optimization: optimized PoweredRailBlock logic - -Original project: https://github.com/FxMorin/RailOptimization - -Full Rewrite of the powered rail iteration logic -that makes powered/activator rails turning on/off up to 4x faster. -This rewrite brings a massive performance boost while keeping the vanilla order. This is achieved by running all the -powered rail logic from a single rail instead of each block iterating separately. Which was not only very -expensive but also completely unnecessary and with a lot of massive overhead - -diff --git a/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java b/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java -index bd14c08defe8afc5ceca59d16a5b1dbad178f594..b37ab12d4c51aca1576a14147a959188031d0fd7 100644 ---- a/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java -@@ -29,7 +29,7 @@ public class PoweredRailBlock extends BaseRailBlock { - this.registerDefaultState((BlockState) ((BlockState) ((BlockState) ((BlockState) this.stateDefinition.any()).setValue(PoweredRailBlock.SHAPE, RailShape.NORTH_SOUTH)).setValue(PoweredRailBlock.POWERED, false)).setValue(PoweredRailBlock.WATERLOGGED, false)); - } - -- protected boolean findPoweredRailSignal(Level world, BlockPos pos, BlockState state, boolean flag, int distance) { -+ public boolean findPoweredRailSignal(Level world, BlockPos pos, BlockState state, boolean flag, int distance) { // Leaf - Rail Optimization - protected -> public - if (distance >= world.purpurConfig.railActivationRange) { // Purpur - return false; - } else { -@@ -117,6 +117,12 @@ public class PoweredRailBlock extends BaseRailBlock { - - @Override - protected void updateState(BlockState state, Level world, BlockPos pos, Block neighbor) { -+ // Leaf start - Rail Optimization -+ if (org.dreeam.leaf.config.modules.opt.OptimizedPoweredRails.enabled) { -+ org.dreeam.leaf.optimize.OptimizedPoweredRails.customUpdateState(this, state, world, pos); -+ return; -+ } -+ // Leaf end - Rail Optimization - boolean flag = (Boolean) state.getValue(PoweredRailBlock.POWERED); - boolean flag1 = world.hasNeighborSignal(pos) || this.findPoweredRailSignal(world, pos, state, true, 0) || this.findPoweredRailSignal(world, pos, state, false, 0); - -diff --git a/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizedPoweredRails.java b/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizedPoweredRails.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ded3c385fcfc6c35086f76bed1b2223a6a29a43e ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizedPoweredRails.java -@@ -0,0 +1,18 @@ -+package org.dreeam.leaf.config.modules.opt; -+ -+import org.dreeam.leaf.config.ConfigModules; -+import org.dreeam.leaf.config.EnumConfigCategory; -+ -+public class OptimizedPoweredRails extends ConfigModules { -+ -+ public String getBasePath() { -+ return EnumConfigCategory.PERF.getBaseKeyName() + ".optimized-powered-rails"; -+ } -+ -+ public static boolean enabled = true; -+ -+ @Override -+ public void onLoaded() { -+ enabled = config().getBoolean(getBasePath(), enabled); -+ } -+} -diff --git a/src/main/java/org/dreeam/leaf/optimize/OptimizedPoweredRails.java b/src/main/java/org/dreeam/leaf/optimize/OptimizedPoweredRails.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9e8bc27585e204aa2df77a90418bbe9e00bcd040 ---- /dev/null -+++ b/src/main/java/org/dreeam/leaf/optimize/OptimizedPoweredRails.java -@@ -0,0 +1,335 @@ -+package org.dreeam.leaf.optimize; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.PoweredRailBlock; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.block.state.properties.RailShape; -+ -+import java.util.HashMap; -+ -+import static net.minecraft.world.level.block.Block.*; -+import static net.minecraft.world.level.block.PoweredRailBlock.POWERED; -+import static net.minecraft.world.level.block.PoweredRailBlock.SHAPE; -+ -+public class OptimizedPoweredRails { -+ -+ private static final Direction[] EAST_WEST_DIR = new Direction[]{Direction.WEST, Direction.EAST}; -+ private static final Direction[] NORTH_SOUTH_DIR = new Direction[]{Direction.SOUTH, Direction.NORTH}; -+ -+ private static final int UPDATE_FORCE_PLACE = UPDATE_MOVE_BY_PISTON | UPDATE_KNOWN_SHAPE | UPDATE_CLIENTS; -+ -+ public static int RAIL_POWER_LIMIT = 8; -+ -+ public static void giveShapeUpdate(Level level, BlockState state, BlockPos pos, BlockPos fromPos, Direction direction) { -+ BlockState oldState = level.getBlockState(pos); -+ Block.updateOrDestroy( -+ oldState, -+ oldState.updateShape(direction.getOpposite(), state, level, pos, fromPos), -+ level, -+ pos, -+ UPDATE_CLIENTS & -34, -+ 0 -+ ); -+ } -+ -+ public static void setRailPowerLimit(int powerLimit) { -+ RAIL_POWER_LIMIT = powerLimit; -+ } -+ -+ public static void customUpdateState(PoweredRailBlock self, BlockState state, Level level, BlockPos pos) { -+ boolean shouldBePowered = level.hasNeighborSignal(pos) || -+ self.findPoweredRailSignal(level, pos, state, true, 0) || -+ self.findPoweredRailSignal(level, pos, state, false, 0); -+ if (shouldBePowered != state.getValue(POWERED)) { -+ RailShape railShape = state.getValue(SHAPE); -+ if (railShape.isAscending()) { -+ level.setBlock(pos, state.setValue(POWERED, shouldBePowered), 3); -+ level.updateNeighborsAtExceptFromFacing(pos.below(), self, Direction.UP); -+ level.updateNeighborsAtExceptFromFacing(pos.above(), self, Direction.DOWN); //isAscending -+ } else if (shouldBePowered) { -+ powerLane(self, level, pos, state, railShape); -+ } else { -+ dePowerLane(self, level, pos, state, railShape); -+ } -+ } -+ } -+ -+ public static boolean findPoweredRailSignalFaster(PoweredRailBlock self, Level world, BlockPos pos, -+ boolean bl, int distance, RailShape shape, -+ HashMap checkedPos) { -+ BlockState blockState = world.getBlockState(pos); -+ boolean speedCheck = checkedPos.containsKey(pos) && checkedPos.get(pos); -+ if (speedCheck) { -+ return world.hasNeighborSignal(pos) || -+ findPoweredRailSignalFaster(self, world, pos, blockState, bl, distance + 1, checkedPos); -+ } else { -+ if (blockState.is(self)) { -+ RailShape railShape = blockState.getValue(SHAPE); -+ if (shape == RailShape.EAST_WEST && ( -+ railShape == RailShape.NORTH_SOUTH || -+ railShape == RailShape.ASCENDING_NORTH || -+ railShape == RailShape.ASCENDING_SOUTH -+ ) || shape == RailShape.NORTH_SOUTH && ( -+ railShape == RailShape.EAST_WEST || -+ railShape == RailShape.ASCENDING_EAST || -+ railShape == RailShape.ASCENDING_WEST -+ )) { -+ return false; -+ } else if (blockState.getValue(POWERED)) { -+ return world.hasNeighborSignal(pos) || -+ findPoweredRailSignalFaster(self, world, pos, blockState, bl, distance + 1, checkedPos); -+ } else { -+ return false; -+ } -+ } -+ return false; -+ } -+ } -+ -+ public static boolean findPoweredRailSignalFaster(PoweredRailBlock self, Level level, -+ BlockPos pos, BlockState state, boolean bl, int distance, -+ HashMap checkedPos) { -+ if (distance >= RAIL_POWER_LIMIT - 1) return false; -+ int i = pos.getX(); -+ int j = pos.getY(); -+ int k = pos.getZ(); -+ boolean bl2 = true; -+ RailShape railShape = state.getValue(SHAPE); -+ switch (railShape.ordinal()) { -+ case 0 -> { -+ if (bl) ++k; -+ else --k; -+ } -+ case 1 -> { -+ if (bl) --i; -+ else ++i; -+ } -+ case 2 -> { -+ if (bl) { -+ --i; -+ } else { -+ ++i; -+ ++j; -+ bl2 = false; -+ } -+ railShape = RailShape.EAST_WEST; -+ } -+ case 3 -> { -+ if (bl) { -+ --i; -+ ++j; -+ bl2 = false; -+ } else { -+ ++i; -+ } -+ railShape = RailShape.EAST_WEST; -+ } -+ case 4 -> { -+ if (bl) { -+ ++k; -+ } else { -+ --k; -+ ++j; -+ bl2 = false; -+ } -+ railShape = RailShape.NORTH_SOUTH; -+ } -+ case 5 -> { -+ if (bl) { -+ ++k; -+ ++j; -+ bl2 = false; -+ } else { -+ --k; -+ } -+ railShape = RailShape.NORTH_SOUTH; -+ } -+ } -+ return findPoweredRailSignalFaster( -+ self, level, new BlockPos(i, j, k), -+ bl, distance, railShape, checkedPos -+ ) || -+ (bl2 && findPoweredRailSignalFaster( -+ self, level, new BlockPos(i, j - 1, k), -+ bl, distance, railShape, checkedPos -+ )); -+ } -+ -+ public static void powerLane(PoweredRailBlock self, Level world, BlockPos pos, -+ BlockState mainState, RailShape railShape) { -+ world.setBlock(pos, mainState.setValue(POWERED, true), UPDATE_FORCE_PLACE); -+ HashMap checkedPos = new HashMap<>(); -+ checkedPos.put(pos, true); -+ int[] count = new int[2]; -+ if (railShape == RailShape.NORTH_SOUTH) { //Order: +z, -z -+ for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) { -+ setRailPositionsPower(self, world, pos, checkedPos, count, i, NORTH_SOUTH_DIR[i]); -+ } -+ updateRails(self, false, world, pos, mainState, count); -+ } else if (railShape == RailShape.EAST_WEST) { //Order: -x, +x -+ for (int i = 0; i < EAST_WEST_DIR.length; ++i) { -+ setRailPositionsPower(self, world, pos, checkedPos, count, i, EAST_WEST_DIR[i]); -+ } -+ updateRails(self, true, world, pos, mainState, count); -+ } -+ } -+ -+ public static void dePowerLane(PoweredRailBlock self, Level world, BlockPos pos, -+ BlockState mainState, RailShape railShape) { -+ world.setBlock(pos, mainState.setValue(POWERED, false), UPDATE_FORCE_PLACE); -+ int[] count = new int[2]; -+ if (railShape == RailShape.NORTH_SOUTH) { //Order: +z, -z -+ for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) { -+ setRailPositionsDePower(self, world, pos, count, i, NORTH_SOUTH_DIR[i]); -+ } -+ updateRails(self, false, world, pos, mainState, count); -+ } else if (railShape == RailShape.EAST_WEST) { //Order: -x, +x -+ for (int i = 0; i < EAST_WEST_DIR.length; ++i) { -+ setRailPositionsDePower(self, world, pos, count, i, EAST_WEST_DIR[i]); -+ } -+ updateRails(self, true, world, pos, mainState, count); -+ } -+ } -+ -+ private static void setRailPositionsPower(PoweredRailBlock self, Level world, BlockPos pos, -+ HashMap checkedPos, int[] count, int i, Direction dir) { -+ for (int z = 1; z < RAIL_POWER_LIMIT; z++) { -+ BlockPos newPos = pos.relative(dir, z); -+ BlockState state = world.getBlockState(newPos); -+ if (checkedPos.containsKey(newPos)) { -+ if (!checkedPos.get(newPos)) break; -+ count[i]++; -+ } else if (!state.is(self) || state.getValue(POWERED) || !( -+ world.hasNeighborSignal(newPos) || -+ findPoweredRailSignalFaster(self, world, newPos, state, true, 0, checkedPos) || -+ findPoweredRailSignalFaster(self, world, newPos, state, false, 0, checkedPos) -+ )) { -+ checkedPos.put(newPos, false); -+ break; -+ } else { -+ checkedPos.put(newPos, true); -+ world.setBlock(newPos, state.setValue(POWERED, true), UPDATE_FORCE_PLACE); -+ count[i]++; -+ } -+ } -+ } -+ -+ private static void setRailPositionsDePower(PoweredRailBlock self, Level world, BlockPos pos, -+ int[] count, int i, Direction dir) { -+ for (int z = 1; z < RAIL_POWER_LIMIT; z++) { -+ BlockPos newPos = pos.relative(dir, z); -+ BlockState state = world.getBlockState(newPos); -+ if (!state.is(self) || !state.getValue(POWERED) || world.hasNeighborSignal(newPos) || -+ self.findPoweredRailSignal(world, newPos, state, true, 0) || -+ self.findPoweredRailSignal(world, newPos, state, false, 0)) break; -+ world.setBlock(newPos, state.setValue(POWERED, false), UPDATE_FORCE_PLACE); -+ count[i]++; -+ } -+ } -+ -+ private static void shapeUpdateEnd(PoweredRailBlock self, Level world, BlockPos pos, BlockState mainState, -+ int endPos, Direction direction, int currentPos, BlockPos blockPos) { -+ if (currentPos == endPos) { -+ BlockPos newPos = pos.relative(direction, currentPos + 1); -+ OptimizedPoweredRails.giveShapeUpdate(world, mainState, newPos, pos, direction); -+ BlockState state = world.getBlockState(blockPos); -+ if (state.is(self) && state.getValue(SHAPE).isAscending()) -+ OptimizedPoweredRails.giveShapeUpdate(world, mainState, newPos.above(), pos, direction); -+ } -+ } -+ -+ private static void neighborUpdateEnd(PoweredRailBlock self, Level world, BlockPos pos, int endPos, -+ Direction direction, Block block, int currentPos, BlockPos blockPos) { -+ if (currentPos == endPos) { -+ BlockPos newPos = pos.relative(direction, currentPos + 1); -+ world.neighborChanged(newPos, block, pos); -+ BlockState state = world.getBlockState(blockPos); -+ if (state.is(self) && state.getValue(SHAPE).isAscending()) -+ world.neighborChanged(newPos.above(), block, blockPos); -+ } -+ } -+ -+ private static void updateRailsSectionEastWestShape(PoweredRailBlock self, Level world, BlockPos pos, -+ int c, BlockState mainState, Direction dir, -+ int[] count, int countAmt) { -+ BlockPos pos1 = pos.relative(dir, c); -+ if (c == 0 && count[1] == 0) -+ giveShapeUpdate(world, mainState, pos1.relative(dir.getOpposite()), pos, dir.getOpposite()); -+ shapeUpdateEnd(self, world, pos, mainState, countAmt, dir, c, pos1); -+ giveShapeUpdate(world, mainState, pos1.below(), pos, Direction.DOWN); -+ giveShapeUpdate(world, mainState, pos1.above(), pos, Direction.UP); -+ giveShapeUpdate(world, mainState, pos1.north(), pos, Direction.NORTH); -+ giveShapeUpdate(world, mainState, pos1.south(), pos, Direction.SOUTH); -+ } -+ -+ private static void updateRailsSectionNorthSouthShape(PoweredRailBlock self, Level world, BlockPos pos, -+ int c, BlockState mainState, Direction dir, -+ int[] count, int countAmt) { -+ BlockPos pos1 = pos.relative(dir, c); -+ giveShapeUpdate(world, mainState, pos1.west(), pos, Direction.WEST); -+ giveShapeUpdate(world, mainState, pos1.east(), pos, Direction.EAST); -+ giveShapeUpdate(world, mainState, pos1.below(), pos, Direction.DOWN); -+ giveShapeUpdate(world, mainState, pos1.above(), pos, Direction.UP); -+ shapeUpdateEnd(self, world, pos, mainState, countAmt, dir, c, pos1); -+ if (c == 0 && count[1] == 0) -+ giveShapeUpdate(world, mainState, pos1.relative(dir.getOpposite()), pos, dir.getOpposite()); -+ } -+ -+ private static void updateRails(PoweredRailBlock self, boolean eastWest, Level world, -+ BlockPos pos, BlockState mainState, int[] count) { -+ if (eastWest) { -+ for (int i = 0; i < EAST_WEST_DIR.length; ++i) { -+ int countAmt = count[i]; -+ if (i == 1 && countAmt == 0) continue; -+ Direction dir = EAST_WEST_DIR[i]; -+ Block block = mainState.getBlock(); -+ for (int c = countAmt; c >= i; c--) { -+ BlockPos p = pos.relative(dir, c); -+ if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()), block, pos); -+ neighborUpdateEnd(self, world, pos, countAmt, dir, block, c, p); -+ world.neighborChanged(p.below(), block, pos); -+ world.neighborChanged(p.above(), block, pos); -+ world.neighborChanged(p.north(), block, pos); -+ world.neighborChanged(p.south(), block, pos); -+ BlockPos pos2 = pos.relative(dir, c).below(); -+ world.neighborChanged(pos2.below(), block, pos); -+ world.neighborChanged(pos2.north(), block, pos); -+ world.neighborChanged(pos2.south(), block, pos); -+ if (c == countAmt) world.neighborChanged(pos.relative(dir, c + 1).below(), block, pos); -+ if (c == 0 && count[1] == 0) -+ world.neighborChanged(p.relative(dir.getOpposite()).below(), block, pos); -+ } -+ for (int c = countAmt; c >= i; c--) -+ updateRailsSectionEastWestShape(self, world, pos, c, mainState, dir, count, countAmt); -+ } -+ } else { -+ for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) { -+ int countAmt = count[i]; -+ if (i == 1 && countAmt == 0) continue; -+ Direction dir = NORTH_SOUTH_DIR[i]; -+ Block block = mainState.getBlock(); -+ for (int c = countAmt; c >= i; c--) { -+ BlockPos p = pos.relative(dir, c); -+ world.neighborChanged(p.west(), block, pos); -+ world.neighborChanged(p.east(), block, pos); -+ world.neighborChanged(p.below(), block, pos); -+ world.neighborChanged(p.above(), block, pos); -+ neighborUpdateEnd(self, world, pos, countAmt, dir, block, c, p); -+ if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()), block, pos); -+ BlockPos pos2 = pos.relative(dir, c).below(); -+ world.neighborChanged(pos2.west(), block, pos); -+ world.neighborChanged(pos2.east(), block, pos); -+ world.neighborChanged(pos2.below(), block, pos); -+ if (c == countAmt) world.neighborChanged(pos.relative(dir, c + 1).below(), block, pos); -+ if (c == 0 && count[1] == 0) -+ world.neighborChanged(p.relative(dir.getOpposite()).below(), block, pos); -+ } -+ for (int c = countAmt; c >= i; c--) -+ updateRailsSectionNorthSouthShape(self, world, pos, c, mainState, dir, count, countAmt); -+ } -+ } -+ } -+} -\ No newline at end of file diff --git a/leaf-server/minecraft-patches/features/0099-Purpur-Server-Minecraft-Changes.patch b/leaf-server/minecraft-patches/features/0099-Purpur-Server-Minecraft-Changes.patch index cffe5dfd..7f7bcb78 100644 --- a/leaf-server/minecraft-patches/features/0099-Purpur-Server-Minecraft-Changes.patch +++ b/leaf-server/minecraft-patches/features/0099-Purpur-Server-Minecraft-Changes.patch @@ -15750,13 +15750,13 @@ index 248ac9bc820a96fc7653471308b18834fc735a77..ef70ba88e492904c426c7d35df442fa6 } // CraftBukkit end diff --git a/net/minecraft/world/level/block/PoweredRailBlock.java b/net/minecraft/world/level/block/PoweredRailBlock.java -index 5ec12356e9f044d99762bf64a6d3bf74856d97a6..e80069d27ff60e0967d9333e2a0d6c8bf2f20a16 100644 +index 6c64fc260669266869f7495ff07e1270dcb4ac75..a2202d2b4352be07b2445064339c61ba6a2521c7 100644 --- a/net/minecraft/world/level/block/PoweredRailBlock.java +++ b/net/minecraft/world/level/block/PoweredRailBlock.java @@ -28,7 +28,7 @@ public class PoweredRailBlock extends BaseRailBlock { } - protected boolean findPoweredRailSignal(Level level, BlockPos pos, BlockState state, boolean searchForward, int recursionCount) { + public boolean findPoweredRailSignal(Level level, BlockPos pos, BlockState state, boolean searchForward, int recursionCount) { - if (recursionCount >= 8) { + if (recursionCount >= level.purpurConfig.railActivationRange) { // Purpur - Config for powered rail activation distance return false; diff --git a/leaf-server/minecraft-patches/features/0100-Fix-Pufferfish-and-Purpur-patches.patch b/leaf-server/minecraft-patches/features/0100-Fix-Pufferfish-and-Purpur-patches.patch index ddc7059c..b92d9cc6 100644 --- a/leaf-server/minecraft-patches/features/0100-Fix-Pufferfish-and-Purpur-patches.patch +++ b/leaf-server/minecraft-patches/features/0100-Fix-Pufferfish-and-Purpur-patches.patch @@ -65,7 +65,7 @@ index 35fd539eb2bfe60ad17ab1e558a01273666acc54..445bbdc8da7f1fdbddfc4d8787d78fea this.values[this.vp++ & 0xFF] = (int)(l * 100L / Runtime.getRuntime().maxMemory()); this.repaint(); diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index d2c1da7df4961fddbb7db952d817f127ece275d9..39d4ae0ae1ec89007fe1c86dc8d3409902890552 100644 +index 35ca166964e8436154891708f69ac010491b64aa..586c00610fdba178f27391820d623c3a5254529f 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -1289,7 +1289,7 @@ public class ServerGamePacketListenerImpl @@ -78,7 +78,7 @@ index d2c1da7df4961fddbb7db952d817f127ece275d9..39d4ae0ae1ec89007fe1c86dc8d34099 this.disconnectAsync(Component.literal("Book too large!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause // Paper - add proper async disconnect return; diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index 30e341bd5fca8b95d6911ca94f4aac17962f3269..55854dbacef9b3da9f12f67be21dd49007ea3c45 100644 +index b714c64a318d38e309351f34426406c70d35d384..4e3b73bee5dae2b5921db86cc53b4cd9ebbd06f8 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -522,23 +522,36 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess @@ -128,7 +128,7 @@ index 30e341bd5fca8b95d6911ca94f4aac17962f3269..55854dbacef9b3da9f12f67be21dd490 public Entity(EntityType entityType, Level level) { this.type = entityType; diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java -index eb17ef1e0c350e97149452dbdc79398d71370fb1..d265278a56575529198735c113fa72c53b541669 100644 +index cd8c764f9aa2321c6e157abe958d89868a9f7bd1..ed15b5f29658d799a36dcbd196a8fcb107be4bda 100644 --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java @@ -1024,13 +1024,13 @@ public abstract class LivingEntity extends Entity implements Attackable { @@ -150,7 +150,7 @@ index eb17ef1e0c350e97149452dbdc79398d71370fb1..d265278a56575529198735c113fa72c5 } // Purpur end - Mob head visibility percent diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java -index ba2f9ab55e52e25788b38c81e1070ae953b66371..33c99f3b24596fbbf3283b455ea8bf340203e01b 100644 +index 6a94f42c8e048985b94db64fa0405e12824a8a0f..fbcf26c0bfb9f496c99bd5a2ba988be06162cd52 100644 --- a/net/minecraft/world/entity/Mob.java +++ b/net/minecraft/world/entity/Mob.java @@ -1505,10 +1505,6 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab @@ -279,7 +279,7 @@ index 584955d151e95727406bc68d6a6f15a33c0f920c..865a83fc73d79893e9bdedad37a6e398 HoglinAi.updateActivity(this); if (this.isConverting()) { diff --git a/net/minecraft/world/entity/monster/piglin/Piglin.java b/net/minecraft/world/entity/monster/piglin/Piglin.java -index bac328ebdf9f94d156211e03d3913157e2b80374..bf1b33b7bb71ec8387d95d9089f199627a041945 100644 +index 0afdfdc07764a26316c171c2a6722caf786875f2..732d44372cf226ca9d008ebc26d1838237ec96d6 100644 --- a/net/minecraft/world/entity/monster/piglin/Piglin.java +++ b/net/minecraft/world/entity/monster/piglin/Piglin.java @@ -357,8 +357,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento @@ -293,7 +293,7 @@ index bac328ebdf9f94d156211e03d3913157e2b80374..bf1b33b7bb71ec8387d95d9089f19962 PiglinAi.updateActivity(this); super.customServerAiStep(level); diff --git a/net/minecraft/world/entity/projectile/Projectile.java b/net/minecraft/world/entity/projectile/Projectile.java -index bde9370798c037c494a9c73f328cb251e68c21b1..3a6be0122d49b20f78be6cc8f1c7acb5c2e45f39 100644 +index 9f1c07be83999b7bafdee9238e8bad8c646d42f1..554e679a756dc1bf529053594231a958717f3573 100644 --- a/net/minecraft/world/entity/projectile/Projectile.java +++ b/net/minecraft/world/entity/projectile/Projectile.java @@ -85,7 +85,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { @@ -305,3 +305,21 @@ index bde9370798c037c494a9c73f328cb251e68c21b1..3a6be0122d49b20f78be6cc8f1c7acb5 } else if (maxProjectileChunkLoadsConfig.perProjectile.resetMovementAfterReachLimit) { this.setDeltaMovement(0, this.getDeltaMovement().y, 0); } +diff --git a/org/purpurmc/purpur/PurpurWorldConfig.java b/org/purpurmc/purpur/PurpurWorldConfig.java +index 31cc4e6e68a9743aaaeb296cb31e64526d3f1f73..cadfbb7310fce33eda24d69c39fda5689c7fb882 100644 +--- a/org/purpurmc/purpur/PurpurWorldConfig.java ++++ b/org/purpurmc/purpur/PurpurWorldConfig.java +@@ -2435,6 +2435,13 @@ public class PurpurWorldConfig { + piglinMobGriefingOverride = getBooleanOrDefault("mobs.piglin.mob-griefing-override", piglinMobGriefingOverride); + piglinTakeDamageFromWater = getBoolean("mobs.piglin.takes-damage-from-water", piglinTakeDamageFromWater); + piglinPortalSpawnModifier = getInt("mobs.piglin.portal-spawn-modifier", piglinPortalSpawnModifier); ++ // Leaf start - Fix Pufferfish and Purpur patches - better input sanitization ++ if (piglinPortalSpawnModifier < 1) { ++ piglinPortalSpawnModifier = 1; ++ log(Level.WARNING, "mobs.piglin.portal-spawn-modifier is set to below minimum allowed value of 1"); ++ log(Level.WARNING, "Using value of 1 to prevent issues"); ++ } ++ // Leaf end - Fix Pufferfish and Purpur patches - better input sanitization + piglinAlwaysDropExp = getBoolean("mobs.piglin.always-drop-exp", piglinAlwaysDropExp); + piglinHeadVisibilityPercent = getDouble("mobs.piglin.head-visibility-percent", piglinHeadVisibilityPercent); + piglinIgnoresArmorWithGoldTrim = getBoolean("mobs.piglin.ignores-armor-with-gold-trim", piglinIgnoresArmorWithGoldTrim); diff --git a/leaf-server/minecraft-patches/features/0113-Petal-Async-Pathfinding.patch b/leaf-server/minecraft-patches/features/0113-Petal-Async-Pathfinding.patch index fcb5068d..ccd7d83c 100644 --- a/leaf-server/minecraft-patches/features/0113-Petal-Async-Pathfinding.patch +++ b/leaf-server/minecraft-patches/features/0113-Petal-Async-Pathfinding.patch @@ -19,7 +19,7 @@ This patch was ported downstream from the Petal fork. Makes most pathfinding-related work happen asynchronously diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java -index 33c99f3b24596fbbf3283b455ea8bf340203e01b..e9ebf23b0b1af85e3738a70acc0eaa3e8980261f 100644 +index fbcf26c0bfb9f496c99bd5a2ba988be06162cd52..cf136bc3d0d285ebde23c6e31c002933564fdcb2 100644 --- a/net/minecraft/world/entity/Mob.java +++ b/net/minecraft/world/entity/Mob.java @@ -243,6 +243,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab @@ -560,19 +560,20 @@ index 1f96fd5085bacb4c584576c7cb9f51e7898e9b03..03b6c8c8dcd42e864751e68be9d35d20 + // Leaf end - Kaiiju - await on async path processing } diff --git a/net/minecraft/world/entity/animal/Bee.java b/net/minecraft/world/entity/animal/Bee.java -index e9dfff7e3726cd2229f89bb39fa1ca4815d99a6d..654ecd20ef4a43f7ffc447c15434ce46ab89efe9 100644 +index e9dfff7e3726cd2229f89bb39fa1ca4815d99a6d..04c0dad21c4cdd464a8ebe830bcdd217bb4156ec 100644 --- a/net/minecraft/world/entity/animal/Bee.java +++ b/net/minecraft/world/entity/animal/Bee.java -@@ -936,7 +936,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { +@@ -936,7 +936,8 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { } else { Bee.this.pathfindRandomlyTowards(Bee.this.hivePos); } - } else { -+ } else if (navigation.getPath() != null && navigation.getPath().isProcessed()) { // Kaiiju - petal - check processing ++ //} else if (navigation.getPath() != null && navigation.getPath().isProcessed()) { // Kaiiju - petal - check processing // todo ++ } else { boolean flag = this.pathfindDirectlyTowards(Bee.this.hivePos); if (!flag) { this.dropAndBlacklistHive(); -@@ -990,7 +990,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { +@@ -990,7 +991,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { return true; } else { Path path = Bee.this.navigation.getPath(); diff --git a/leaf-server/minecraft-patches/features/0117-Configurable-movement-speed-of-more-entities.patch b/leaf-server/minecraft-patches/features/0117-Configurable-movement-speed-of-more-entities.patch index bcd3649e..4b0b4c32 100644 --- a/leaf-server/minecraft-patches/features/0117-Configurable-movement-speed-of-more-entities.patch +++ b/leaf-server/minecraft-patches/features/0117-Configurable-movement-speed-of-more-entities.patch @@ -83,7 +83,7 @@ index 39489c8a347031fb4f73faca46039786e35762ac..4de1d0966157b20526386becee10b9be } diff --git a/org/purpurmc/purpur/PurpurWorldConfig.java b/org/purpurmc/purpur/PurpurWorldConfig.java -index 31cc4e6e68a9743aaaeb296cb31e64526d3f1f73..67739ba7cb82d615a5f0c4db7aaa5552c53a42c5 100644 +index cadfbb7310fce33eda24d69c39fda5689c7fb882..21765347d7a81f4111f23685f699286d5e5cccb6 100644 --- a/org/purpurmc/purpur/PurpurWorldConfig.java +++ b/org/purpurmc/purpur/PurpurWorldConfig.java @@ -1625,6 +1625,7 @@ public class PurpurWorldConfig { @@ -118,7 +118,7 @@ index 31cc4e6e68a9743aaaeb296cb31e64526d3f1f73..67739ba7cb82d615a5f0c4db7aaa5552 } public boolean illusionerRidable = false; -@@ -3417,6 +3421,7 @@ public class PurpurWorldConfig { +@@ -3424,6 +3428,7 @@ public class PurpurWorldConfig { public boolean zombieTakeDamageFromWater = false; public boolean zombieAlwaysDropExp = false; public double zombieHeadVisibilityPercent = 0.5D; @@ -126,7 +126,7 @@ index 31cc4e6e68a9743aaaeb296cb31e64526d3f1f73..67739ba7cb82d615a5f0c4db7aaa5552 private void zombieSettings() { zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable); zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater); -@@ -3442,6 +3447,7 @@ public class PurpurWorldConfig { +@@ -3449,6 +3454,7 @@ public class PurpurWorldConfig { zombieTakeDamageFromWater = getBoolean("mobs.zombie.takes-damage-from-water", zombieTakeDamageFromWater); zombieAlwaysDropExp = getBoolean("mobs.zombie.always-drop-exp", zombieAlwaysDropExp); zombieHeadVisibilityPercent = getDouble("mobs.zombie.head-visibility-percent", zombieHeadVisibilityPercent); @@ -134,7 +134,7 @@ index 31cc4e6e68a9743aaaeb296cb31e64526d3f1f73..67739ba7cb82d615a5f0c4db7aaa5552 } public boolean zombieHorseRidable = false; -@@ -3491,6 +3497,7 @@ public class PurpurWorldConfig { +@@ -3498,6 +3504,7 @@ public class PurpurWorldConfig { public int zombieVillagerCuringTimeMax = 6000; public boolean zombieVillagerCureEnabled = true; public boolean zombieVillagerAlwaysDropExp = false; @@ -142,7 +142,7 @@ index 31cc4e6e68a9743aaaeb296cb31e64526d3f1f73..67739ba7cb82d615a5f0c4db7aaa5552 private void zombieVillagerSettings() { zombieVillagerRidable = getBoolean("mobs.zombie_villager.ridable", zombieVillagerRidable); zombieVillagerRidableInWater = getBoolean("mobs.zombie_villager.ridable-in-water", zombieVillagerRidableInWater); -@@ -3511,6 +3518,7 @@ public class PurpurWorldConfig { +@@ -3518,6 +3525,7 @@ public class PurpurWorldConfig { zombieVillagerCuringTimeMax = getInt("mobs.zombie_villager.curing_time.max", zombieVillagerCuringTimeMax); zombieVillagerCureEnabled = getBoolean("mobs.zombie_villager.cure.enabled", zombieVillagerCureEnabled); zombieVillagerAlwaysDropExp = getBoolean("mobs.zombie_villager.always-drop-exp", zombieVillagerAlwaysDropExp); @@ -150,7 +150,7 @@ index 31cc4e6e68a9743aaaeb296cb31e64526d3f1f73..67739ba7cb82d615a5f0c4db7aaa5552 } public boolean zombifiedPiglinRidable = false; -@@ -3525,6 +3533,7 @@ public class PurpurWorldConfig { +@@ -3532,6 +3540,7 @@ public class PurpurWorldConfig { public boolean zombifiedPiglinCountAsPlayerKillWhenAngry = false; public boolean zombifiedPiglinTakeDamageFromWater = false; public boolean zombifiedPiglinAlwaysDropExp = false; @@ -158,7 +158,7 @@ index 31cc4e6e68a9743aaaeb296cb31e64526d3f1f73..67739ba7cb82d615a5f0c4db7aaa5552 private void zombifiedPiglinSettings() { zombifiedPiglinRidable = getBoolean("mobs.zombified_piglin.ridable", zombifiedPiglinRidable); zombifiedPiglinRidableInWater = getBoolean("mobs.zombified_piglin.ridable-in-water", zombifiedPiglinRidableInWater); -@@ -3546,6 +3555,7 @@ public class PurpurWorldConfig { +@@ -3553,6 +3562,7 @@ public class PurpurWorldConfig { zombifiedPiglinCountAsPlayerKillWhenAngry = getBoolean("mobs.zombified_piglin.count-as-player-kill-when-angry", zombifiedPiglinCountAsPlayerKillWhenAngry); zombifiedPiglinTakeDamageFromWater = getBoolean("mobs.zombified_piglin.takes-damage-from-water", zombifiedPiglinTakeDamageFromWater); zombifiedPiglinAlwaysDropExp = getBoolean("mobs.zombified_piglin.always-drop-exp", zombifiedPiglinAlwaysDropExp); diff --git a/leaf-server/minecraft-patches/features/0121-Plazma-Add-missing-purpur-configuration-options.patch b/leaf-server/minecraft-patches/features/0121-Plazma-Add-missing-purpur-configuration-options.patch index afcc7772..8674d65d 100644 --- a/leaf-server/minecraft-patches/features/0121-Plazma-Add-missing-purpur-configuration-options.patch +++ b/leaf-server/minecraft-patches/features/0121-Plazma-Add-missing-purpur-configuration-options.patch @@ -263,7 +263,7 @@ index 234f123959830cc2adb78b9dc8752906140e5b11..e0dceff32b47b334ddcb76271e3cf3ea org.bukkit.event.inventory.InventoryType.ENDER_CHEST.setDefaultSize(enderChestSixRows ? 54 : 27); enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows); diff --git a/org/purpurmc/purpur/PurpurWorldConfig.java b/org/purpurmc/purpur/PurpurWorldConfig.java -index 67739ba7cb82d615a5f0c4db7aaa5552c53a42c5..d81e06592aa9521f866c2c910b43a7364de3796e 100644 +index 21765347d7a81f4111f23685f699286d5e5cccb6..8459f5b9bf548e51b85e753a4e65dd86baa2b5df 100644 --- a/org/purpurmc/purpur/PurpurWorldConfig.java +++ b/org/purpurmc/purpur/PurpurWorldConfig.java @@ -1189,12 +1189,20 @@ public class PurpurWorldConfig { @@ -353,7 +353,7 @@ index 67739ba7cb82d615a5f0c4db7aaa5552c53a42c5..d81e06592aa9521f866c2c910b43a736 } public boolean ghastRidable = false; -@@ -2911,6 +2945,10 @@ public class PurpurWorldConfig { +@@ -2918,6 +2952,10 @@ public class PurpurWorldConfig { public double snifferMaxHealth = 14.0D; public double snifferScale = 1.0D; public int snifferBreedingTicks = 6000; @@ -364,7 +364,7 @@ index 67739ba7cb82d615a5f0c4db7aaa5552c53a42c5..d81e06592aa9521f866c2c910b43a736 private void snifferSettings() { snifferRidable = getBoolean("mobs.sniffer.ridable", snifferRidable); snifferRidableInWater = getBoolean("mobs.sniffer.ridable-in-water", snifferRidableInWater); -@@ -2918,6 +2956,10 @@ public class PurpurWorldConfig { +@@ -2925,6 +2963,10 @@ public class PurpurWorldConfig { snifferMaxHealth = getDouble("mobs.sniffer.attributes.max_health", snifferMaxHealth); snifferScale = Mth.clamp(getDouble("mobs.sniffer.attributes.scale", snifferScale), 0.0625D, 16.0D); snifferBreedingTicks = getInt("mobs.sniffer.breeding-delay-ticks", snifferBreedingTicks); @@ -375,7 +375,7 @@ index 67739ba7cb82d615a5f0c4db7aaa5552c53a42c5..d81e06592aa9521f866c2c910b43a736 } public boolean squidRidable = false; -@@ -3019,10 +3061,20 @@ public class PurpurWorldConfig { +@@ -3026,10 +3068,20 @@ public class PurpurWorldConfig { public boolean tadpoleRidable = false; public boolean tadpoleRidableInWater = true; public boolean tadpoleControllable = true; @@ -396,7 +396,7 @@ index 67739ba7cb82d615a5f0c4db7aaa5552c53a42c5..d81e06592aa9521f866c2c910b43a736 } public boolean traderLlamaRidable = false; -@@ -3256,10 +3308,20 @@ public class PurpurWorldConfig { +@@ -3263,10 +3315,20 @@ public class PurpurWorldConfig { public boolean wardenRidable = false; public boolean wardenRidableInWater = true; public boolean wardenControllable = true; diff --git a/leaf-server/minecraft-patches/features/0158-Reduce-worldgen-allocations.patch b/leaf-server/minecraft-patches/features/0158-Reduce-worldgen-allocations.patch index d741c54a..cee918c6 100644 --- a/leaf-server/minecraft-patches/features/0158-Reduce-worldgen-allocations.patch +++ b/leaf-server/minecraft-patches/features/0158-Reduce-worldgen-allocations.patch @@ -34,7 +34,7 @@ index f861f9e087182470a3bbb22678dbdacb8a73e943..a3d0d17178eedfaef83e2e0df6b1c2d7 private DensityFunction wrapNew(DensityFunction densityFunction) { diff --git a/net/minecraft/world/level/levelgen/SurfaceRules.java b/net/minecraft/world/level/levelgen/SurfaceRules.java -index 0948c8db90605a15a043b5c5bc74edecd7f9db1b..009e8a270c25614d03413d8b8b1f39c2da8ba12f 100644 +index 0948c8db90605a15a043b5c5bc74edecd7f9db1b..6cba88415a4715527e163e54662db9b3ab37c747 100644 --- a/net/minecraft/world/level/levelgen/SurfaceRules.java +++ b/net/minecraft/world/level/levelgen/SurfaceRules.java @@ -313,8 +313,15 @@ public class SurfaceRules { @@ -48,9 +48,9 @@ index 0948c8db90605a15a043b5c5bc74edecd7f9db1b..009e8a270c25614d03413d8b8b1f39c2 + ++this.lastUpdateY; + Supplier> getter = this.biome; + if (getter == null) { -+ this.biome = getter = new org.dreeam.leaf.util.biome.PositionalBiomeGetter(this.biomeGetter, this.pos); ++ this.biome = getter = new org.dreeam.leaf.world.biome.PositionalBiomeGetter(this.biomeGetter, this.pos); + } -+ ((org.dreeam.leaf.util.biome.PositionalBiomeGetter) getter).update(blockX, blockY, blockZ); ++ ((org.dreeam.leaf.world.biome.PositionalBiomeGetter) getter).update(blockX, blockY, blockZ); + // Leaf end - Reduce worldgen allocations this.blockY = blockY; this.waterHeight = waterHeight; diff --git a/leaf-server/minecraft-patches/features/0170-Multithreaded-Tracker.patch b/leaf-server/minecraft-patches/features/0170-Multithreaded-Tracker.patch index 8da412f2..b6e9fb9c 100644 --- a/leaf-server/minecraft-patches/features/0170-Multithreaded-Tracker.patch +++ b/leaf-server/minecraft-patches/features/0170-Multithreaded-Tracker.patch @@ -23,6 +23,28 @@ for the case of some NPC plugins which using real entity type, e.g. Citizens. But it is still recommending to use those packet based, virtual entity based NPC plugins, e.g. ZNPC Plus, Adyeshach, Fancy NPC, etc. +diff --git a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java +index 1b8193587814225c2ef2c5d9e667436eb50ff6c5..4200d22606c6a3dbdf282792a4007a51df66963b 100644 +--- a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java ++++ b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java +@@ -60,7 +60,16 @@ public final class NearbyPlayers { + + private final ServerLevel world; + private final Reference2ReferenceOpenHashMap players = new Reference2ReferenceOpenHashMap<>(); +- private final Long2ReferenceOpenHashMap byChunk = new Long2ReferenceOpenHashMap<>(); ++ // Leaf start - Multithreaded tracker ++ private final it.unimi.dsi.fastutil.longs.Long2ReferenceMap byChunk; ++ { ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { ++ byChunk = it.unimi.dsi.fastutil.longs.Long2ReferenceMaps.synchronize(new Long2ReferenceOpenHashMap<>()); ++ } else { ++ byChunk = new Long2ReferenceOpenHashMap<>(); ++ } ++ } ++ // Leaf end - Multithreaded tracker + private final Long2ReferenceOpenHashMap>[] directByChunk = new Long2ReferenceOpenHashMap[TOTAL_MAP_TYPES]; + { + for (int i = 0; i < this.directByChunk.length; ++i) { diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java index 02a9ef1694c796584c29430d27f0a09047368835..32608df3da169159c070f37cb55407f4f6187744 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java @@ -36,8 +58,20 @@ index 02a9ef1694c796584c29430d27f0a09047368835..32608df3da169159c070f37cb55407f4 private static final byte CHUNK_TICKET_STAGE_NONE = 0; private static final byte CHUNK_TICKET_STAGE_LOADING = 1; +diff --git a/net/minecraft/network/protocol/game/ClientboundUpdateAttributesPacket.java b/net/minecraft/network/protocol/game/ClientboundUpdateAttributesPacket.java +index 9c0c99b936b4a82ebfe924866e53ec71f7bbe9ad..2ccff968cb2065d34fad4d27573f9e3081edb2f2 100644 +--- a/net/minecraft/network/protocol/game/ClientboundUpdateAttributesPacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundUpdateAttributesPacket.java +@@ -32,6 +32,7 @@ public class ClientboundUpdateAttributesPacket implements Packet seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl + // Leaf start - Multithreaded tracker + public static final ServerPlayerConnection[] EMPTY_OBJECT_ARRAY = new ServerPlayerConnection[0]; -+ public final Object sync = new Object(); + public final Set seenBy = org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled ? it.unimi.dsi.fastutil.objects.ReferenceSets.synchronize(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>()) : new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl + private volatile ServerPlayerConnection[] seenByArray = EMPTY_OBJECT_ARRAY; + public ServerPlayerConnection[] seenBy() { @@ -90,7 +123,7 @@ index c60b9e4076450de2157c1a3cf4f98cc2c19e4e6a..41da66fd77924a9a4bd9cc12f517e2e6 // Paper start - optimise entity tracker private long lastChunkUpdate = -1L; -@@ -1162,27 +1189,95 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1162,27 +1188,95 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.lastTrackedChunk = chunk; final ServerPlayer[] playersRaw = players.getRawDataUnchecked(); @@ -191,7 +224,7 @@ index c60b9e4076450de2157c1a3cf4f98cc2c19e4e6a..41da66fd77924a9a4bd9cc12f517e2e6 if (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(conn.getPlayer())) { foundToRemove = true; break; -@@ -1193,12 +1288,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1193,12 +1287,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider return; } @@ -207,7 +240,7 @@ index c60b9e4076450de2157c1a3cf4f98cc2c19e4e6a..41da66fd77924a9a4bd9cc12f517e2e6 } @Override -@@ -1208,10 +1304,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1208,10 +1303,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider if (this.seenBy.isEmpty()) { return; } @@ -221,7 +254,7 @@ index c60b9e4076450de2157c1a3cf4f98cc2c19e4e6a..41da66fd77924a9a4bd9cc12f517e2e6 } @Override -@@ -1238,7 +1335,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1238,7 +1334,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void broadcast(Packet packet) { @@ -230,7 +263,7 @@ index c60b9e4076450de2157c1a3cf4f98cc2c19e4e6a..41da66fd77924a9a4bd9cc12f517e2e6 serverPlayerConnection.send(packet); } } -@@ -1259,21 +1356,34 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1259,21 +1355,34 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } public void broadcastRemoved() { @@ -268,7 +301,7 @@ index c60b9e4076450de2157c1a3cf4f98cc2c19e4e6a..41da66fd77924a9a4bd9cc12f517e2e6 // Paper start - remove allocation of Vec3D here // Vec3 vec3 = player.position().subtract(this.entity.position()); double vec3_dx = player.getX() - this.entity.getX(); -@@ -1301,6 +1411,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1301,6 +1410,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider // CraftBukkit end if (flag) { if (this.seenBy.add(player.connection)) { @@ -276,7 +309,7 @@ index c60b9e4076450de2157c1a3cf4f98cc2c19e4e6a..41da66fd77924a9a4bd9cc12f517e2e6 // Paper start - entity tracking events if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) { this.serverEntity.addPairing(player); -@@ -1309,6 +1420,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1309,6 +1419,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.serverEntity.onPlayerAdd(); // Paper - fix desync when a player is added to the tracker } } else if (this.seenBy.remove(player.connection)) { @@ -298,18 +331,10 @@ index f106373ef3ac4a8685c2939c9e8361688a285913..51ae390c68e7a3aa193329cc3bc47ca6 public boolean visible = true; diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java -index 1a9601aee097b6c10cf2ae1c52fddf45da85f60f..867936866d952c559b6ffa49fdf78acd70a9bab9 100644 +index 1a9601aee097b6c10cf2ae1c52fddf45da85f60f..16b2ca8c96e9561aa57e0903d1e98e6441044b6d 100644 --- a/net/minecraft/server/level/ServerEntity.java +++ b/net/minecraft/server/level/ServerEntity.java -@@ -75,6 +75,7 @@ public class ServerEntity { - @Nullable - private List> trackedDataValues; - private final Set trackedPlayers; // Paper -+ public boolean wantSendDirtyEntityData = false; // Leaf - Multithreaded tracker - - public ServerEntity( - ServerLevel level, -@@ -146,7 +147,7 @@ public class ServerEntity { +@@ -146,7 +146,7 @@ public class ServerEntity { MapId mapId = itemFrame.cachedMapId; // Paper - Perf: Cache map ids on item frames MapItemSavedData savedData = MapItem.getSavedData(mapId, this.level); if (savedData != null) { @@ -318,21 +343,17 @@ index 1a9601aee097b6c10cf2ae1c52fddf45da85f60f..867936866d952c559b6ffa49fdf78acd final ServerPlayer serverPlayer = connection.getPlayer(); // Paper savedData.tickCarriedBy(serverPlayer, item); Packet updatePacket = savedData.getUpdatePacket(mapId, serverPlayer); -@@ -450,6 +451,12 @@ public class ServerEntity { +@@ -468,7 +468,7 @@ public class ServerEntity { + this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); + } + +- attributesToSync.clear(); ++ // attributesToSync.clear(); // Leaf - Multithreaded tracker + } } - public void sendDirtyEntityData() { -+ // Leaf start - Multithreaded tracker -+ if (Thread.currentThread() instanceof org.dreeam.leaf.async.tracker.MultithreadedTracker.MultithreadedTrackerThread) { -+ wantSendDirtyEntityData = true; -+ return; -+ } -+ // Leaf end - Multithreaded tracker - SynchedEntityData entityData = this.entity.getEntityData(); - List> list = entityData.packDirty(); - if (list != null) { diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 5943b18f172fb1d77ef1fe768daa8e8f43c3c8c1..7b85a9ebdbe3e8bee0a8fc100ede8a3f07eee5ce 100644 +index 42f56bd6a9f449df23f7e7f00b3efa114a003449..0471b04833fbca5dfc0cc6575692cdefb83edbed 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -2503,7 +2503,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -354,7 +375,7 @@ index 5943b18f172fb1d77ef1fe768daa8e8f43c3c8c1..7b85a9ebdbe3e8bee0a8fc100ede8a3f } } diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 729f595491c7a4edf24dff2e876dfb69ade87a17..a3069b29a1b78012314747d705e27c167acd10b3 100644 +index be803fd0a79850254bd4a7f64534142cd5b4ec98..6992a53abdcff6299bedf33ba0b1bc6386a1ea74 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -1878,7 +1878,7 @@ public class ServerGamePacketListenerImpl @@ -366,6 +387,233 @@ index 729f595491c7a4edf24dff2e876dfb69ade87a17..a3069b29a1b78012314747d705e27c16 // Paper start - Prevent teleporting dead entities if (this.player.isRemoved()) { LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java +index 4c3eadc2d8480b2a2c2c08e58620544d403d3adc..68241d1d488bc2848e3c0d167270c1788e573c37 100644 +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -1317,13 +1317,13 @@ public abstract class LivingEntity extends Entity implements Attackable { + } + + private void refreshDirtyAttributes() { +- Set attributesToUpdate = this.getAttributes().getAttributesToUpdate(); ++ // Leaf start - Multithreaded tracker ++ int[] attributesToUpdate = this.getAttributes().getAttributesToUpdateIds(); + +- for (AttributeInstance attributeInstance : attributesToUpdate) { +- this.onAttributeUpdated(attributeInstance.getAttribute()); ++ for (int attribute : attributesToUpdate) { ++ this.onAttributeUpdated(net.minecraft.core.registries.BuiltInRegistries.ATTRIBUTE.get(attribute).orElseThrow()); + } +- +- attributesToUpdate.clear(); ++ // Leaf end - Multithreaded tracker + } + + protected void onAttributeUpdated(Holder attribute) { +diff --git a/net/minecraft/world/entity/ai/attributes/Attribute.java b/net/minecraft/world/entity/ai/attributes/Attribute.java +index f8419dde44ebc7324e783f8bee42132d5ec973c3..406767c60ec1a324faaf5d3658b161647497f99b 100644 +--- a/net/minecraft/world/entity/ai/attributes/Attribute.java ++++ b/net/minecraft/world/entity/ai/attributes/Attribute.java +@@ -16,10 +16,15 @@ public class Attribute { + private boolean syncable; + private final String descriptionId; + private Attribute.Sentiment sentiment = Attribute.Sentiment.POSITIVE; ++ // Leaf start - Optimize AttributeMap ++ public final int uid; ++ private static final java.util.concurrent.atomic.AtomicInteger SIZE = new java.util.concurrent.atomic.AtomicInteger(); ++ // Leaf end - Optimize AttributeMap + + protected Attribute(String descriptionId, double defaultValue) { + this.defaultValue = defaultValue; + this.descriptionId = descriptionId; ++ this.uid = SIZE.getAndAdd(1); // Leaf - Optimize AttributeMap + } + + public double getDefaultValue() { +diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +index 3ac9f36eae87369354e992a1d9b5c5b2d87d17cb..9f09d78a7dac12c7f1b06029d32ad93fae0c2aec 100644 +--- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +@@ -26,8 +26,24 @@ public class AttributeInstance { + private final Map> modifiersByOperation = Maps.newEnumMap( + AttributeModifier.Operation.class + ); +- private final Map modifierById = new Object2ObjectArrayMap<>(); +- private final Map permanentModifiers = new Object2ObjectArrayMap<>(); ++ // Leaf start - Multithreaded tracker ++ private final Map modifierById; ++ { ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { ++ modifierById = it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this); ++ } else { ++ modifierById = new Object2ObjectArrayMap<>(); ++ } ++ } ++ private final Map permanentModifiers; ++ { ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { ++ permanentModifiers = it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this); ++ } else { ++ permanentModifiers = new Object2ObjectArrayMap<>(); ++ } ++ } ++ // Leaf end - Multithreaded tracker + private double baseValue; + private boolean dirty = true; + private double cachedValue; +@@ -56,7 +72,13 @@ public class AttributeInstance { + + @VisibleForTesting + Map getModifiers(AttributeModifier.Operation operation) { +- return this.modifiersByOperation.computeIfAbsent(operation, operation1 -> new Object2ObjectOpenHashMap<>()); ++ // Leaf start - Multithreaded tracker ++ return this.modifiersByOperation.computeIfAbsent(operation, operation1 -> { ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) ++ return it.unimi.dsi.fastutil.objects.Object2ObjectMaps.synchronize(new Object2ObjectArrayMap<>(), this); ++ else return new Object2ObjectArrayMap<>(); ++ }); ++ // Leaf end - Multithreaded tracker + } + + public Set getModifiers() { +@@ -144,8 +166,12 @@ public class AttributeInstance { + + public double getValue() { + if (this.dirty) { +- this.cachedValue = this.calculateValue(); ++ // Leaf start - Multithreaded tracker ++ double value = this.calculateValue(); ++ this.cachedValue = value; + this.dirty = false; ++ return value; ++ // Leaf end - Multithreaded tracker + } + + return this.cachedValue; +@@ -192,7 +218,15 @@ public class AttributeInstance { + compoundTag.store("id", TYPE_CODEC, this.attribute); + compoundTag.putDouble("base", this.baseValue); + if (!this.permanentModifiers.isEmpty()) { +- compoundTag.store("modifiers", AttributeModifier.CODEC.listOf(), List.copyOf(this.permanentModifiers.values())); ++ // Leaf start - Multithreaded tracker ++ if (org.dreeam.leaf.config.modules.async.MultithreadedTracker.enabled) { ++ synchronized (this) { ++ compoundTag.store("modifiers", AttributeModifier.CODEC.listOf(), List.copyOf(this.permanentModifiers.values())); ++ } ++ } else { ++ compoundTag.store("modifiers", AttributeModifier.CODEC.listOf(), List.copyOf(this.permanentModifiers.values())); ++ } ++ // Leaf end - Multithreaded tracker + } + + return compoundTag; +diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java +index 701025715e0aca3c1f920a66f9b3d03ec08eaf02..778872cdd1717f1ad52de32faf43847a08c55269 100644 +--- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java +@@ -15,12 +15,14 @@ import net.minecraft.resources.ResourceLocation; + + public class AttributeMap { + // Gale start - Lithium - replace AI attributes with optimized collections +- private final Map, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); +- private final Set attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); +- private final Set attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); ++ // Leaf start - Multithreaded tracker ++ private final Map, AttributeInstance> attributes = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(); ++ private final org.dreeam.leaf.util.map.AttributeInstanceSet attributesToSync = new org.dreeam.leaf.util.map.AttributeInstanceSet((org.dreeam.leaf.util.map.AttributeInstanceArrayMap) attributes); ++ private final org.dreeam.leaf.util.map.AttributeInstanceSet attributesToUpdate = new org.dreeam.leaf.util.map.AttributeInstanceSet((org.dreeam.leaf.util.map.AttributeInstanceArrayMap) attributes); ++ // Leaf end - Multithreaded tracker + // Gale end - Lithium - replace AI attributes with optimized collections + private final AttributeSupplier supplier; +- private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations ++ //private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations // Leaf - Optimize AttributeMap + private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables + + public AttributeMap(AttributeSupplier supplier) { +@@ -31,31 +33,54 @@ public class AttributeMap { + this.entity = entity; + // Purpur end - Ridables + this.supplier = defaultAttributes; +- this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations ++ //this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations // Leaf - Optimize AttributeMap + } + +- private void onAttributeModified(AttributeInstance instance) { ++ // Leaf start - Multithreaded tracker ++ private synchronized void onAttributeModified(AttributeInstance instance) { + this.attributesToUpdate.add(instance); + if (instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))) { // Purpur - Ridables + this.attributesToSync.add(instance); + } + } + +- public Set getAttributesToSync() { +- return this.attributesToSync; ++ private static final AttributeInstance[] EMPTY_ATTRIBUTE_INSTANCE = new AttributeInstance[0]; ++ public synchronized Set getAttributesToSync() { ++ var clone = it.unimi.dsi.fastutil.objects.ReferenceArraySet.ofUnchecked(attributesToSync.toArray(EMPTY_ATTRIBUTE_INSTANCE)); ++ this.attributesToSync.clear(); ++ return clone; + } + +- public Set getAttributesToUpdate() { +- return this.attributesToUpdate; ++ public synchronized Set getAttributesToUpdate() { ++ var clone = it.unimi.dsi.fastutil.objects.ReferenceArraySet.ofUnchecked(attributesToUpdate.toArray(EMPTY_ATTRIBUTE_INSTANCE)); ++ this.attributesToUpdate.clear(); ++ return clone; + } + ++ public synchronized int[] getAttributesToUpdateIds() { ++ int[] clone = attributesToUpdate.inner.toIntArray(); ++ this.attributesToUpdate.clear(); ++ return clone; ++ } ++ // Leaf end - Multithreaded tracker ++ + public Collection getSyncableAttributes() { + return this.attributes.values().stream().filter(instance -> instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))).collect(Collectors.toList()); // Purpur - Ridables + } + + @Nullable + public AttributeInstance getInstance(Holder attribute) { +- return this.attributes.computeIfAbsent(attribute, this.createInstance); // Gale - Airplane - reduce entity allocations - cache lambda, as for some reason java allocates it anyways ++ // Leaf start - Multithreaded tracker ++ AttributeInstance v; ++ if ((v = this.attributes.get(attribute)) == null) { ++ AttributeInstance newValue; ++ if ((newValue = this.supplier.createInstance(this::onAttributeModified, attribute)) != null) { ++ attributes.put(attribute, newValue); ++ return newValue; ++ } ++ } ++ return v; ++ // Leaf end - Multithreaded tracker + } + + public boolean hasAttribute(Holder attribute) { +diff --git a/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java b/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java +index 24710041ccbc70e5506d8d89ae34f0141977f209..05de8a77b389691dd6986f36b4cb8cc0935e21e4 100644 +--- a/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java ++++ b/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java +@@ -11,7 +11,7 @@ public class AttributeSupplier { + private final Map, AttributeInstance> instances; + + AttributeSupplier(Map, AttributeInstance> instances) { +- this.instances = instances; ++ this.instances = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(instances); // Leaf - Optimize AttributeMap + } + + public AttributeInstance getAttributeInstance(Holder attribute) { +@@ -41,7 +41,7 @@ public class AttributeSupplier { + } + + @Nullable +- public AttributeInstance createInstance(Consumer onDirty, Holder attribute) { ++ public AttributeInstance createInstance(Consumer onDirty, Holder attribute) { // Leaf - Multithreaded tracker + AttributeInstance attributeInstance = this.instances.get(attribute); + if (attributeInstance == null) { + return null; diff --git a/net/minecraft/world/entity/item/PrimedTnt.java b/net/minecraft/world/entity/item/PrimedTnt.java index 36e3937c9e09852937c94c268c877a15337835c5..8aedc3ca463745fe32cac977208b23dc0b8e73b6 100644 --- a/net/minecraft/world/entity/item/PrimedTnt.java diff --git a/leaf-server/minecraft-patches/features/0190-Cache-chunk-key.patch b/leaf-server/minecraft-patches/features/0190-Cache-chunk-key.patch index 34b994e8..1f734f68 100644 --- a/leaf-server/minecraft-patches/features/0190-Cache-chunk-key.patch +++ b/leaf-server/minecraft-patches/features/0190-Cache-chunk-key.patch @@ -9,10 +9,10 @@ This patch didn't cahce SectionPos or BlockPos to chunkKey, since it needs to co TODO: Cache block pos and section pos, whether need? diff --git a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -index 1b8193587814225c2ef2c5d9e667436eb50ff6c5..288a3eb57f3431dd624ad8a4b08684563abbc5ad 100644 +index 4200d22606c6a3dbdf282792a4007a51df66963b..4b258f048c73107d0d050a9aa4b4a39788145b17 100644 --- a/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java +++ b/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java -@@ -127,7 +127,7 @@ public final class NearbyPlayers { +@@ -136,7 +136,7 @@ public final class NearbyPlayers { } public TrackedChunk getChunk(final ChunkPos pos) { @@ -21,7 +21,7 @@ index 1b8193587814225c2ef2c5d9e667436eb50ff6c5..288a3eb57f3431dd624ad8a4b0868456 } public TrackedChunk getChunk(final BlockPos pos) { -@@ -143,7 +143,7 @@ public final class NearbyPlayers { +@@ -152,7 +152,7 @@ public final class NearbyPlayers { } public ReferenceList getPlayers(final ChunkPos pos, final NearbyMapType type) { @@ -97,7 +97,7 @@ index fd3d0f6cb53bc8b6186f0d86575f21007b2c20ed..cddeeab73e7b981701a42c5aad6b4777 // Paper end - rewrite chunk system } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 7b85a9ebdbe3e8bee0a8fc100ede8a3f07eee5ce..c8c99323b6397c3e595e7a9007e5d801ee2ac14a 100644 +index 0471b04833fbca5dfc0cc6575692cdefb83edbed..1f3020f5a051d5f4e5c8fa088f844962c3105573 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -503,7 +503,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe diff --git a/leaf-server/minecraft-patches/features/0222-Use-BFS-on-getSlopeDistance.patch b/leaf-server/minecraft-patches/features/0222-Use-BFS-on-getSlopeDistance.patch index 43717629..46ad5a98 100644 --- a/leaf-server/minecraft-patches/features/0222-Use-BFS-on-getSlopeDistance.patch +++ b/leaf-server/minecraft-patches/features/0222-Use-BFS-on-getSlopeDistance.patch @@ -8,11 +8,26 @@ Paper: ~75ms Leaf: ~48ms (-36%) This should help drastically on the farms that use actively changing fluids. +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index c6eb136e8db0aa232681ac9bd28c4b70fbbacb40..706e198631e4a3b0a5d2fc3f58060e4a15701ecd 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -1300,6 +1300,10 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + this.emptyTime = 0; + } + ++ // Leaf start - Use BFS on getSlopeDistance ++ public it.unimi.dsi.fastutil.longs.LongSet slopeDistanceCacheVisited = new it.unimi.dsi.fastutil.longs.LongOpenHashSet(512); ++ public net.minecraft.world.level.material.FlowingFluid.SlopeDistanceNodeDeque slopeDistanceCacheQueue = new net.minecraft.world.level.material.FlowingFluid.SlopeDistanceNodeDeque(); ++ // Leaf end - Use BFS on getSlopeDistance + private void tickFluid(BlockPos pos, Fluid fluid) { + BlockState blockState = this.getBlockState(pos); + FluidState fluidState = blockState.getFluidState(); diff --git a/net/minecraft/world/level/material/FlowingFluid.java b/net/minecraft/world/level/material/FlowingFluid.java -index 4625bd55e1cb01dfb9921dcd033f05b4a8f9ad74..c0f78556d112e59333ace60f1522e7fd1efe71c3 100644 +index 4625bd55e1cb01dfb9921dcd033f05b4a8f9ad74..a1c6a30c1e446955b409d1f00c602db06c1e9813 100644 --- a/net/minecraft/world/level/material/FlowingFluid.java +++ b/net/minecraft/world/level/material/FlowingFluid.java -@@ -342,32 +342,81 @@ public abstract class FlowingFluid extends Fluid { +@@ -342,32 +342,124 @@ public abstract class FlowingFluid extends Fluid { protected abstract void beforeDestroyingBlock(LevelAccessor level, BlockPos pos, BlockState state); @@ -20,13 +35,15 @@ index 4625bd55e1cb01dfb9921dcd033f05b4a8f9ad74..c0f78556d112e59333ace60f1522e7fd - int i = 1000; + // Leaf start - Use BFS on getSlopeDistance + protected int getSlopeDistance(LevelReader level, BlockPos startPos, int initialDepth, Direction excludedDirection, BlockState startState, FlowingFluid.SpreadContext spreadContext) { -+ it.unimi.dsi.fastutil.longs.LongSet visited = new it.unimi.dsi.fastutil.longs.LongOpenHashSet(512); -+ java.util.Queue queue = new java.util.ArrayDeque<>(256); ++ it.unimi.dsi.fastutil.longs.LongSet visited = ((ServerLevel) level).slopeDistanceCacheVisited; ++ SlopeDistanceNodeDeque queue = ((ServerLevel) level).slopeDistanceCacheQueue; ++ visited.clear(); ++ queue.clear(); + + for (Direction dir : Direction.Plane.HORIZONTAL) { + if (dir == excludedDirection) continue; + -+ BlockPos neighborPos = startPos.relative(dir); ++ BlockPos neighborPos = startPos.relative(dir); // immutable + BlockState neighborState = spreadContext.getBlockStateIfLoaded(neighborPos); + if (neighborState == null) continue; + @@ -72,7 +89,7 @@ index 4625bd55e1cb01dfb9921dcd033f05b4a8f9ad74..c0f78556d112e59333ace60f1522e7fd + for (Direction dir : Direction.Plane.HORIZONTAL) { + if (dir == current.excludedDir) continue; + -+ BlockPos nextPos = current.pos.relative(dir); ++ BlockPos nextPos = current.pos.relative(dir); // immutable + BlockState nextState = spreadContext.getBlockStateIfLoaded(nextPos); + if (nextState == null) continue; + @@ -96,18 +113,59 @@ index 4625bd55e1cb01dfb9921dcd033f05b4a8f9ad74..c0f78556d112e59333ace60f1522e7fd + return ((long) pos.getX() & 0xFFFFFFFFL) << 32 | ((long) pos.getZ() & 0xFFFFFFFFL) << 4 | (excludedDir.ordinal() & 0x0F); + } + -+ private static class SlopeDistanceNode { -+ final BlockPos pos; -+ final int depth; -+ final Direction excludedDir; -+ final BlockState state; ++ public static class SlopeDistanceNodeDeque { ++ private SlopeDistanceNode[] array; ++ private int length; ++ private int start; ++ private int end; + -+ SlopeDistanceNode(BlockPos pos, int depth, Direction excludedDir, BlockState state) { -+ this.pos = pos.immutable(); -+ this.depth = depth; -+ this.excludedDir = excludedDir; -+ this.state = state; ++ public SlopeDistanceNodeDeque() { ++ array = new SlopeDistanceNode[256]; ++ length = array.length; + } ++ ++ /* ++ private int size() { ++ int apparent = end - start; ++ return apparent >= 0 ? apparent : length + apparent; ++ } ++ */ ++ ++ private void clear() { ++ start = 0; ++ end = 0; ++ } ++ ++ private boolean isEmpty() { ++ return end == start || (end <= start && length == start - end); ++ } ++ ++ private SlopeDistanceNode poll() { ++ final SlopeDistanceNode t = array[start]; ++ if (++start == length) start = 0; ++ return t; ++ } ++ ++ private void add(final SlopeDistanceNode node) { ++ array[end++] = node; ++ if (end == length) end = 0; ++ if (end == start) resize(length, 2 * length); ++ } ++ ++ private void resize(final int size, final int newLength) { ++ final SlopeDistanceNode[] newArray = new SlopeDistanceNode[newLength]; ++ if (size != 0) { ++ System.arraycopy(array, start, newArray, 0, length - start); ++ System.arraycopy(array, 0, newArray, length - start, end); ++ } ++ start = 0; ++ end = size; ++ array = newArray; ++ length = newLength; ++ } ++ } ++ ++ private record SlopeDistanceNode(BlockPos pos, int depth, Direction excludedDir, BlockState state) { } + // Leaf end - Use BFS on getSlopeDistance diff --git a/leaf-server/minecraft-patches/features/0226-Optimize-addOrUpdateTransientModifier.patch b/leaf-server/minecraft-patches/features/0226-Optimize-addOrUpdateTransientModifier.patch index 2bb178ef..c96f2425 100644 --- a/leaf-server/minecraft-patches/features/0226-Optimize-addOrUpdateTransientModifier.patch +++ b/leaf-server/minecraft-patches/features/0226-Optimize-addOrUpdateTransientModifier.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Optimize addOrUpdateTransientModifier diff --git a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -index 3ac9f36eae87369354e992a1d9b5c5b2d87d17cb..c39654d3e1e5573646b3729502e4eae1a6dba7c9 100644 +index 9f09d78a7dac12c7f1b06029d32ad93fae0c2aec..2af5cd6f2b9541cb28075fda014350235a4f72c7 100644 --- a/net/minecraft/world/entity/ai/attributes/AttributeInstance.java +++ b/net/minecraft/world/entity/ai/attributes/AttributeInstance.java -@@ -87,8 +87,13 @@ public class AttributeInstance { +@@ -109,8 +109,13 @@ public class AttributeInstance { } public void addOrUpdateTransientModifier(AttributeModifier modifier) { diff --git a/leaf-server/minecraft-patches/features/0236-Async-target-finding.patch b/leaf-server/minecraft-patches/features/0236-Async-target-finding.patch index b8ba01ff..2a4f2c28 100644 --- a/leaf-server/minecraft-patches/features/0236-Async-target-finding.patch +++ b/leaf-server/minecraft-patches/features/0236-Async-target-finding.patch @@ -149,7 +149,7 @@ index 54910c2e1d6e6bb556e536fda060bd09402e04e8..747eb54f84650a9a507398e3d5352e00 // Gale start - Pufferfish - SIMD support diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 1346cff0018e6d17cb892892c3aeebfb86453b60..a753481afe02a2367c378c7f39206fde23eda00d 100644 +index 1d2c27a7595701895aa3c96339814fd1454a50dd..db3bff36c812a00fc35ebcc6ac6d11ce24040003 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -173,7 +173,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @@ -220,7 +220,7 @@ index 1346cff0018e6d17cb892892c3aeebfb86453b60..a753481afe02a2367c378c7f39206fde } // Paper - rewrite chunk system } -@@ -1312,6 +1337,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1316,6 +1341,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } // Paper end - rewrite chunk system @@ -228,7 +228,7 @@ index 1346cff0018e6d17cb892892c3aeebfb86453b60..a753481afe02a2367c378c7f39206fde } private void tickBlock(BlockPos pos, Block block) { -@@ -1325,6 +1351,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1329,6 +1355,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } // Paper end - rewrite chunk system diff --git a/leaf-server/minecraft-patches/features/0244-Protocol-Core.patch b/leaf-server/minecraft-patches/features/0244-Protocol-Core.patch index e00c88dc..df23ab64 100644 --- a/leaf-server/minecraft-patches/features/0244-Protocol-Core.patch +++ b/leaf-server/minecraft-patches/features/0244-Protocol-Core.patch @@ -35,10 +35,10 @@ index 78aee57ad8224b0728411c699d2e3844847c9c79..8f5b400bdf5c1c194f75ee98e2f1e984 this.tickables.get(i).run(); } diff --git a/net/minecraft/server/level/ServerEntity.java b/net/minecraft/server/level/ServerEntity.java -index 867936866d952c559b6ffa49fdf78acd70a9bab9..b1953701583ba88c524368a52988fea6fb1ab8d2 100644 +index 16b2ca8c96e9561aa57e0903d1e98e6441044b6d..939400c18eb4e87e0bf1b131e1601f4dcaa4885c 100644 --- a/net/minecraft/server/level/ServerEntity.java +++ b/net/minecraft/server/level/ServerEntity.java -@@ -298,6 +298,7 @@ public class ServerEntity { +@@ -297,6 +297,7 @@ public class ServerEntity { this.entity.hurtMarked = false; this.broadcastAndSend(new ClientboundSetEntityMotionPacket(this.entity)); } diff --git a/leaf-server/minecraft-patches/features/0257-Optimize-AttributeMap.patch b/leaf-server/minecraft-patches/features/0257-Optimize-AttributeMap.patch deleted file mode 100644 index 4bce8f29..00000000 --- a/leaf-server/minecraft-patches/features/0257-Optimize-AttributeMap.patch +++ /dev/null @@ -1,86 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: hayanesuru -Date: Thu, 15 May 2025 21:11:18 +0900 -Subject: [PATCH] Optimize AttributeMap - - -diff --git a/net/minecraft/world/entity/ai/attributes/Attribute.java b/net/minecraft/world/entity/ai/attributes/Attribute.java -index f8419dde44ebc7324e783f8bee42132d5ec973c3..406767c60ec1a324faaf5d3658b161647497f99b 100644 ---- a/net/minecraft/world/entity/ai/attributes/Attribute.java -+++ b/net/minecraft/world/entity/ai/attributes/Attribute.java -@@ -16,10 +16,15 @@ public class Attribute { - private boolean syncable; - private final String descriptionId; - private Attribute.Sentiment sentiment = Attribute.Sentiment.POSITIVE; -+ // Leaf start - Optimize AttributeMap -+ public final int uid; -+ private static final java.util.concurrent.atomic.AtomicInteger SIZE = new java.util.concurrent.atomic.AtomicInteger(); -+ // Leaf end - Optimize AttributeMap - - protected Attribute(String descriptionId, double defaultValue) { - this.defaultValue = defaultValue; - this.descriptionId = descriptionId; -+ this.uid = SIZE.getAndAdd(1); // Leaf - Optimize AttributeMap - } - - public double getDefaultValue() { -diff --git a/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/net/minecraft/world/entity/ai/attributes/AttributeMap.java -index 701025715e0aca3c1f920a66f9b3d03ec08eaf02..1ef934b02bd99c0fe997e9ec163457b39dba55f0 100644 ---- a/net/minecraft/world/entity/ai/attributes/AttributeMap.java -+++ b/net/minecraft/world/entity/ai/attributes/AttributeMap.java -@@ -15,12 +15,12 @@ import net.minecraft.resources.ResourceLocation; - - public class AttributeMap { - // Gale start - Lithium - replace AI attributes with optimized collections -- private final Map, AttributeInstance> attributes = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(0); -+ private final Map, AttributeInstance> attributes = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(); // Leaf - Optimize AttributeMap - private final Set attributesToSync = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); - private final Set attributesToUpdate = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(0); - // Gale end - Lithium - replace AI attributes with optimized collections - private final AttributeSupplier supplier; -- private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations -+ //private final java.util.function.Function, AttributeInstance> createInstance; // Gale - Airplane - reduce entity allocations // Leaf - Optimize AttributeMap - private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables - - public AttributeMap(AttributeSupplier supplier) { -@@ -31,7 +31,7 @@ public class AttributeMap { - this.entity = entity; - // Purpur end - Ridables - this.supplier = defaultAttributes; -- this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations -+ //this.createInstance = holder -> this.supplier.createInstance(this::onAttributeModified, holder); // Gale - Airplane - reduce entity allocations // Leaf - Optimize AttributeMap - } - - private void onAttributeModified(AttributeInstance instance) { -@@ -55,7 +55,17 @@ public class AttributeMap { - - @Nullable - public AttributeInstance getInstance(Holder attribute) { -- return this.attributes.computeIfAbsent(attribute, this.createInstance); // Gale - Airplane - reduce entity allocations - cache lambda, as for some reason java allocates it anyways -+ // Leaf start - Optimize AttributeMap -+ AttributeInstance v; -+ if ((v = this.attributes.get(attribute)) == null) { -+ AttributeInstance newValue; -+ if ((newValue = this.supplier.createInstance(this::onAttributeModified, attribute)) != null) { -+ this.attributes.put(attribute, newValue); -+ return newValue; -+ } -+ } -+ return v; -+ // Leaf end - Optimize AttributeMap - } - - public boolean hasAttribute(Holder attribute) { -diff --git a/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java b/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java -index 24710041ccbc70e5506d8d89ae34f0141977f209..09341ef6c651150aba223689badbead490162b2b 100644 ---- a/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java -+++ b/net/minecraft/world/entity/ai/attributes/AttributeSupplier.java -@@ -11,7 +11,7 @@ public class AttributeSupplier { - private final Map, AttributeInstance> instances; - - AttributeSupplier(Map, AttributeInstance> instances) { -- this.instances = instances; -+ this.instances = new org.dreeam.leaf.util.map.AttributeInstanceArrayMap(instances); // Leaf - Optimize AttributeMap - } - - public AttributeInstance getAttributeInstance(Holder attribute) { diff --git a/leaf-server/minecraft-patches/features/0258-Optimize-getScaledTrackingDistance.patch b/leaf-server/minecraft-patches/features/0257-Optimize-getScaledTrackingDistance.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0258-Optimize-getScaledTrackingDistance.patch rename to leaf-server/minecraft-patches/features/0257-Optimize-getScaledTrackingDistance.patch diff --git a/leaf-server/minecraft-patches/features/0259-Optimize-SynchedEntityData-packDirty.patch b/leaf-server/minecraft-patches/features/0258-Optimize-SynchedEntityData-packDirty.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0259-Optimize-SynchedEntityData-packDirty.patch rename to leaf-server/minecraft-patches/features/0258-Optimize-SynchedEntityData-packDirty.patch diff --git a/leaf-server/minecraft-patches/features/0260-Optimize-isEyeInFluid.patch b/leaf-server/minecraft-patches/features/0259-Optimize-isEyeInFluid.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0260-Optimize-isEyeInFluid.patch rename to leaf-server/minecraft-patches/features/0259-Optimize-isEyeInFluid.patch diff --git a/leaf-server/minecraft-patches/features/0261-Paw-optimization.patch b/leaf-server/minecraft-patches/features/0260-Paw-optimization.patch similarity index 98% rename from leaf-server/minecraft-patches/features/0261-Paw-optimization.patch rename to leaf-server/minecraft-patches/features/0260-Paw-optimization.patch index cc715b4e..d8448158 100644 --- a/leaf-server/minecraft-patches/features/0261-Paw-optimization.patch +++ b/leaf-server/minecraft-patches/features/0260-Paw-optimization.patch @@ -94,10 +94,10 @@ index 0f9d18dd29e210ad656da211a3cb1cb25cd4efb1..d1c36cd17c83e7e0167046093c4a2b84 for (LevelChunk levelChunk : list) { diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index a753481afe02a2367c378c7f39206fde23eda00d..753b4255d9e9b9628bc5825a6b4f0f2540f06e7a 100644 +index db3bff36c812a00fc35ebcc6ac6d11ce24040003..e8706189de80a2f0e60882fff0fe891165050f38 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -1367,13 +1367,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1371,13 +1371,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // Paper end - log detailed entity tick information public void tickNonPassenger(Entity entity) { @@ -111,7 +111,7 @@ index a753481afe02a2367c378c7f39206fde23eda00d..753b4255d9e9b9628bc5825a6b4f0f25 entity.setOldPosAndRot(); entity.tickCount++; entity.totalEntityAge++; // Paper - age-like counter for all entities -@@ -1386,13 +1380,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -1390,13 +1384,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe for (Entity entity1 : entity.getPassengers()) { this.tickPassenger(entity, entity1, isActive); // Paper - EAR 2 } diff --git a/leaf-server/minecraft-patches/features/0262-Cache-block-path-type.patch b/leaf-server/minecraft-patches/features/0261-Cache-block-path-type.patch similarity index 85% rename from leaf-server/minecraft-patches/features/0262-Cache-block-path-type.patch rename to leaf-server/minecraft-patches/features/0261-Cache-block-path-type.patch index 8b50dc46..dda3b28e 100644 --- a/leaf-server/minecraft-patches/features/0262-Cache-block-path-type.patch +++ b/leaf-server/minecraft-patches/features/0261-Cache-block-path-type.patch @@ -4,18 +4,18 @@ Date: Fri, 23 May 2025 12:01:42 +0900 Subject: [PATCH] Cache block path type -diff --git a/net/minecraft/server/Bootstrap.java b/net/minecraft/server/Bootstrap.java -index 83bc2f6b7774c8753a15dbeb00f0c9103713fd1b..20a720253092b4ec0b648edf06a048f92da201e0 100644 ---- a/net/minecraft/server/Bootstrap.java -+++ b/net/minecraft/server/Bootstrap.java -@@ -60,6 +60,7 @@ public class Bootstrap { - io.papermc.paper.world.worldgen.OptionallyFlatBedrockConditionSource.bootstrap(); // Paper - Flat bedrock generator settings - }); - // Paper end -+ net.minecraft.world.level.block.Blocks.initPathType(); // Leaf - Cache path type - CreativeModeTabs.validate(); - wrapStreams(); - bootstrapDuration.set(Duration.between(instant, Instant.now()).toMillis()); +diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java +index 595284787053a5fb7385e8493953c73a19fe7aee..6dec45b376288638433f0d50e474f9713266d99c 100644 +--- a/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -360,6 +360,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + } + + if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) mobSpawnExecutor.start(); // Pufferfish ++ net.minecraft.world.level.block.Blocks.initPathType(); // Leaf - Cache path type + org.purpurmc.purpur.task.BossBarTask.startAll(); // Purpur - Implement TPSBar + if (org.purpurmc.purpur.PurpurConfig.beeCountPayload) org.purpurmc.purpur.task.BeehiveTask.instance().register(); // Purpur - Give bee counts in beehives to Purpur clients + diff --git a/net/minecraft/world/level/block/Blocks.java b/net/minecraft/world/level/block/Blocks.java index 303bd27d44e4acfee49334235a6704724e3fd616..d001d0859c9508eb06f05010ab1cb8069f9b87cf 100644 --- a/net/minecraft/world/level/block/Blocks.java diff --git a/leaf-server/minecraft-patches/features/0263-optimize-getEntityStatus.patch b/leaf-server/minecraft-patches/features/0262-optimize-getEntityStatus.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0263-optimize-getEntityStatus.patch rename to leaf-server/minecraft-patches/features/0262-optimize-getEntityStatus.patch diff --git a/leaf-server/minecraft-patches/features/0264-Paper-Rewrite-dataconverter-system.patch b/leaf-server/minecraft-patches/features/0263-Paper-Rewrite-dataconverter-system.patch similarity index 100% rename from leaf-server/minecraft-patches/features/0264-Paper-Rewrite-dataconverter-system.patch rename to leaf-server/minecraft-patches/features/0263-Paper-Rewrite-dataconverter-system.patch diff --git a/leaf-server/minecraft-patches/features/0264-Rail-Optimization-optimized-PoweredRailBlock-logic.patch b/leaf-server/minecraft-patches/features/0264-Rail-Optimization-optimized-PoweredRailBlock-logic.patch new file mode 100644 index 00000000..090c228b --- /dev/null +++ b/leaf-server/minecraft-patches/features/0264-Rail-Optimization-optimized-PoweredRailBlock-logic.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Sat, 17 Feb 2024 17:57:08 -0500 +Subject: [PATCH] Rail Optimization: optimized PoweredRailBlock logic + +Original project: https://github.com/FxMorin/RailOptimization + +Full Rewrite of the powered rail iteration logic +that makes powered/activator rails turning on/off up to 4x faster. +This rewrite brings a massive performance boost while keeping the vanilla order. This is achieved by running all the +powered rail logic from a single rail instead of each block iterating separately. Which was not only very +expensive but also completely unnecessary and with a lot of massive overhead + +diff --git a/net/minecraft/world/level/block/PoweredRailBlock.java b/net/minecraft/world/level/block/PoweredRailBlock.java +index a2202d2b4352be07b2445064339c61ba6a2521c7..abf0ffa3540963f591f428876c0c671517594c89 100644 +--- a/net/minecraft/world/level/block/PoweredRailBlock.java ++++ b/net/minecraft/world/level/block/PoweredRailBlock.java +@@ -122,6 +122,12 @@ public class PoweredRailBlock extends BaseRailBlock { + + @Override + protected void updateState(BlockState state, Level level, BlockPos pos, Block block) { ++ // Leaf start - Rail Optimization ++ if (org.dreeam.leaf.config.modules.opt.OptimizedPoweredRails.enabled) { ++ org.dreeam.leaf.world.block.OptimizedPoweredRails.updateState(this, state, level, pos); ++ return; ++ } ++ // Leaf end - Rail Optimization + boolean poweredValue = state.getValue(POWERED); + boolean flag = level.hasNeighborSignal(pos) + || this.findPoweredRailSignal(level, pos, state, true, 0) diff --git a/leaf-server/paper-patches/features/0042-Multithreaded-Tracker.patch b/leaf-server/paper-patches/features/0042-Multithreaded-Tracker.patch index a70c2802..4026b894 100644 --- a/leaf-server/paper-patches/features/0042-Multithreaded-Tracker.patch +++ b/leaf-server/paper-patches/features/0042-Multithreaded-Tracker.patch @@ -71,8 +71,21 @@ index 2a63e8e725fa97da5d4b9fba6bfe19377c7cfbe1..0c286eabd8e4d5e599d93a151874af6b set.add(connection.getPlayer().getBukkitEntity().getPlayer()); } return set; +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index cab15624afaeaca5d69206a0b7fff5da54e5ef29..4533fbbbc35ed92177f7704f243a1614a1773ca1 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2892,7 +2892,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + Iterator iterator = collection.iterator(); + while (iterator.hasNext()) { + AttributeInstance genericInstance = iterator.next(); +- if (genericInstance.getAttribute() == Attributes.MAX_HEALTH) { ++ if (genericInstance != null && genericInstance.getAttribute() == Attributes.MAX_HEALTH) { // Leaf - Multithreaded tracker + iterator.remove(); + break; + } diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index 3a3fbbfa0b82092cd9ac8eab2d179fb9f590aec8..566eab1add471266c8617747c8c25ee04e13c02f 100644 +index 5300a513a295d472752d31a6e8af48bb64b06704..fec0f3b72c1808c5019e95760e3fef19c45e1be0 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -1751,6 +1751,26 @@ public class CraftEventFactory { diff --git a/leaf-server/paper-patches/features/0053-Paw-optimization.patch b/leaf-server/paper-patches/features/0053-Paw-optimization.patch new file mode 100644 index 00000000..62060f49 --- /dev/null +++ b/leaf-server/paper-patches/features/0053-Paw-optimization.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Thu, 24 Apr 2025 16:36:16 -0400 +Subject: [PATCH] Paw optimization + +Some random optimizations + +diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +index 34a2e07dabc546799df6881de30252397d856f05..d8aca1d7ffad3eccaae676263a41a9a1f04988b9 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/data/CraftBlockData.java +@@ -244,13 +244,37 @@ public class CraftBlockData implements BlockData { + + if (!states.isEmpty()) { + stateString.append('['); +- stateString.append(states.entrySet().stream().map(StateHolder.PROPERTY_ENTRY_TO_STRING_FUNCTION).collect(Collectors.joining(","))); ++ // Leaf start - paw optimization ++ int i = 0; ++ for (Map.Entry, Comparable> propertyEntry : states.entrySet()) { ++ if (propertyEntry == null) { ++ stateString.append(""); ++ } else { ++ Property property = propertyEntry.getKey(); ++ Comparable value = propertyEntry.getValue(); ++ ++ stateString.append(property.getName()).append("=").append(getValueName(property, value)); ++ } ++ ++ if (i < states.size() - 1) { ++ stateString.append(","); ++ } ++ ++ i++; ++ } ++ // Leaf end - paw optimization + stateString.append(']'); + } + + return stateString.toString(); + } + ++ // Leaf start - paw optimization ++ private > String getValueName(Property property, Comparable value) { ++ return property.getName((T) value); ++ } ++ // Leaf end - paw optimization ++ + public Map toStates(boolean hideUnspecified) { + return (hideUnspecified && this.parsedStates != null) ? CraftBlockData.toStates(this.parsedStates) : CraftBlockData.toStates(this.state.getValues()); + } diff --git a/leaf-server/paper-patches/features/0054-optimise-ReferenceList.patch b/leaf-server/paper-patches/features/0054-optimise-ReferenceList.patch new file mode 100644 index 00000000..66fcb91c --- /dev/null +++ b/leaf-server/paper-patches/features/0054-optimise-ReferenceList.patch @@ -0,0 +1,35 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sun, 1 Jun 2025 18:13:24 +0200 +Subject: [PATCH] optimise ReferenceList + + +diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java +index 8df9406b77eb3c225ebf88bf76a7adb666452f3b..14ac2a533e0b882f26ee4a11f8d6bccfe752c750 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/list/ReferenceList.java +@@ -47,17 +47,21 @@ public final class ReferenceList implements Iterable { + + // move the object at the end to this index + final int endIndex = --this.count; +- final E end = (E)this.references[endIndex]; + if (index != endIndex) { ++ // The removed element was not the last one. ++ // Move the element that was at 'endIndex' (the old tail) to 'index'. ++ final E end = (E)this.references[endIndex]; + // not empty after this call + this.referenceToIndex.put(end, index); // update index ++ this.references[index] = end; + } +- this.references[index] = end; ++ // Null out the slot at 'endIndex'. ++ // If 'index == endIndex', this was the slot of the removed element. ++ // If 'index != endIndex', this was the original slot of the moved element 'end'. + this.references[endIndex] = null; + + return true; + } +- + public boolean add(final E obj) { + final int count = this.count; + final int currIndex = this.referenceToIndex.putIfAbsent(obj, count); diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java index 084ed5df..780e2678 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/ai/AsyncGoalExecutor.java @@ -18,7 +18,6 @@ public class AsyncGoalExecutor { protected final SpscIntQueue queue; protected final SpscIntQueue wake; protected final IntArrayList submit; - private final AsyncGoalThread thread; private final ServerLevel world; private long midTickCount = 0L; @@ -27,7 +26,6 @@ public class AsyncGoalExecutor { this.queue = new SpscIntQueue(AsyncTargetFinding.queueSize); this.wake = new SpscIntQueue(AsyncTargetFinding.queueSize); this.submit = new IntArrayList(); - this.thread = thread; } boolean wake(int id) { @@ -46,7 +44,18 @@ public class AsyncGoalExecutor { public final void tick() { batchSubmit(); - LockSupport.unpark(thread); + while (true) { + OptionalInt result = this.wake.recv(); + if (result.isEmpty()) { + break; + } + int id = result.getAsInt(); + if (poll(id) && !this.queue.send(id)) { + do { + wake(id); + } while (poll(id)); + } + } } private void batchSubmit() { @@ -67,18 +76,6 @@ public class AsyncGoalExecutor { } public final void midTick() { - while (true) { - OptionalInt result = this.wake.recv(); - if (result.isEmpty()) { - break; - } - int id = result.getAsInt(); - if (poll(id) && !this.queue.send(id)) { - do { - wake(id); - } while (poll(id)); - } - } if (AsyncTargetFinding.threshold <= 0L || (midTickCount % AsyncTargetFinding.threshold) == 0L) { batchSubmit(); } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/chunk/AsyncChunkSend.java b/leaf-server/src/main/java/org/dreeam/leaf/async/chunk/AsyncChunkSend.java index d5fcecaf..55f40ca1 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/chunk/AsyncChunkSend.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/chunk/AsyncChunkSend.java @@ -1,5 +1,6 @@ package org.dreeam.leaf.async.chunk; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import net.minecraft.Util; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -14,13 +15,13 @@ public class AsyncChunkSend { public static final ExecutorService POOL = new ThreadPoolExecutor( 1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), - new com.google.common.util.concurrent.ThreadFactoryBuilder() - .setPriority(Thread.NORM_PRIORITY - 2) + new ThreadFactoryBuilder() + .setPriority(Thread.NORM_PRIORITY) .setNameFormat("Leaf Async Chunk Send Thread") .setUncaughtExceptionHandler(Util::onThreadException) .setThreadFactory(AsyncChunkSendThread::new) .build(), - new ThreadPoolExecutor.DiscardPolicy() + new ThreadPoolExecutor.CallerRunsPolicy() ); public static final Logger LOGGER = LogManager.getLogger("Leaf Async Chunk Send"); } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java b/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java index 12ae72fe..7ebcec15 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/path/AsyncPathProcessor.java @@ -5,6 +5,7 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.pathfinder.Path; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.dreeam.leaf.config.modules.async.AsyncPathfinding; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -29,8 +30,8 @@ public class AsyncPathProcessor { private static long lastWarnMillis = System.currentTimeMillis(); private static final ThreadPoolExecutor pathProcessingExecutor = new ThreadPoolExecutor( 1, - org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingMaxThreads, - org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingKeepalive, TimeUnit.SECONDS, + AsyncPathfinding.asyncPathfindingMaxThreads, + AsyncPathfinding.asyncPathfindingKeepalive, TimeUnit.SECONDS, getQueueImpl(), new ThreadFactoryBuilder() .setNameFormat(THREAD_PREFIX + " Thread - %d") @@ -44,7 +45,7 @@ public class AsyncPathProcessor { public void rejectedExecution(Runnable rejectedTask, ThreadPoolExecutor executor) { BlockingQueue workQueue = executor.getQueue(); if (!executor.isShutdown()) { - switch (org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingRejectPolicy) { + switch (AsyncPathfinding.asyncPathfindingRejectPolicy) { case FLUSH_ALL -> { if (!workQueue.isEmpty()) { List pendingTasks = new ArrayList<>(workQueue.size()); @@ -98,7 +99,7 @@ public class AsyncPathProcessor { } private static BlockingQueue getQueueImpl() { - final int queueCapacity = org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingQueueSize; + final int queueCapacity = AsyncPathfinding.asyncPathfindingQueueSize; return new LinkedBlockingQueue<>(queueCapacity); } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java b/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java index d2025803..e55dd632 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/tracker/MultithreadedTracker.java @@ -32,15 +32,6 @@ public class MultithreadedTracker { private static long lastWarnMillis = System.currentTimeMillis(); private static ThreadPoolExecutor TRACKER_EXECUTOR = null; - private record SendChanges(Object[] entities, int size) implements Runnable { - @Override - public void run() { - for (int i = 0; i < size; i++) { - ((ServerEntity) entities[i]).sendDirtyEntityData(); - } - } - } - private MultithreadedTracker() { } @@ -80,7 +71,6 @@ public class MultithreadedTracker { // Move tracking to off-main TRACKER_EXECUTOR.execute(() -> { - ReferenceArrayList sendDirty = new ReferenceArrayList<>(); for (final Entity entity : trackerEntitiesRaw) { if (entity == null) continue; @@ -88,19 +78,12 @@ public class MultithreadedTracker { if (tracker == null) continue; - // Don't Parallel Tick Tracker of Entity - synchronized (tracker.sync) { - tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition())); + synchronized (tracker) { + var trackedChunk = nearbyPlayers.getChunk(entity.chunkPosition()); + tracker.moonrise$tick(trackedChunk); tracker.serverEntity.sendChanges(); - if (tracker.serverEntity.wantSendDirtyEntityData) { - tracker.serverEntity.wantSendDirtyEntityData = false; - sendDirty.add(tracker.serverEntity); - } } } - if (!sendDirty.isEmpty()) { - level.getServer().execute(new SendChanges(sendDirty.elements(), sendDirty.size())); - } }); } @@ -121,7 +104,7 @@ public class MultithreadedTracker { if (tracker == null) continue; - synchronized (tracker.sync) { + synchronized (tracker) { tickTask[index] = tracker.leafTickCompact(nearbyPlayers.getChunk(entity.chunkPosition())); sendChangesTasks[index] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array } @@ -140,22 +123,6 @@ public class MultithreadedTracker { sendChanges.run(); } - - ReferenceArrayList sendDirty = new ReferenceArrayList<>(); - for (final Entity entity : trackerEntitiesRaw) { - if (entity == null) continue; - - final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity(); - - if (tracker == null) continue; - if (tracker.serverEntity.wantSendDirtyEntityData) { - tracker.serverEntity.wantSendDirtyEntityData = false; - sendDirty.add(tracker.serverEntity); - } - } - if (!sendDirty.isEmpty()) { - level.getServer().execute(new SendChanges(sendDirty.elements(), sendDirty.size())); - } }); } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java index e40a1c50..ad14e420 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java @@ -227,8 +227,12 @@ public class LeafConfig { extraConfigs.addAll(Arrays.asList(existing.split(","))); } + // Use same way in spark's BukkitServerConfigProvider#getNestedFiles to get all world configs + // It may spam in the spark profiler, but it's ok, since spark uses YamlConfigParser.INSTANCE to + // get configs defined in extra config flag instead of using SplitYamlConfigParser.INSTANCE for (World world : Bukkit.getWorlds()) { - extraConfigs.add(world.getWorldFolder().getName() + "/gale-world.yml"); // Gale world config + Path galeWorldFolder = world.getWorldFolder().toPath().resolve("gale-world.yml"); + extraConfigs.add(galeWorldFolder.toString().replace("\\", "/").replace("./", "")); // Gale world config } return extraConfigs; diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java index 1c1c4efc..304e234f 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java @@ -8,15 +8,18 @@ import org.dreeam.leaf.config.annotations.Experimental; public class SparklyPaperParallelWorldTicking extends ConfigModules { public String getBasePath() { - return EnumConfigCategory.ASYNC.getBaseKeyName() + ".parallel-world-tracking"; - } // TODO: Correct config key when stable + return EnumConfigCategory.ASYNC.getBaseKeyName() + ".parallel-world-ticking"; + } @Experimental public static boolean enabled = false; public static int threads = 8; public static boolean logContainerCreationStacktraces = false; public static boolean disableHardThrow = false; + @Deprecated public static boolean runAsyncTasksSync = false; + // STRICT, BUFFERED, DISABLED + public static String asyncUnsafeReadHandling = "BUFFERED"; @Override public void onLoaded() { @@ -34,15 +37,30 @@ public class SparklyPaperParallelWorldTicking extends ConfigModules { } else { threads = 0; } + logContainerCreationStacktraces = config.getBoolean(getBasePath() + ".log-container-creation-stacktraces", logContainerCreationStacktraces); logContainerCreationStacktraces = enabled && logContainerCreationStacktraces; disableHardThrow = config.getBoolean(getBasePath() + ".disable-hard-throw", disableHardThrow); disableHardThrow = enabled && disableHardThrow; - runAsyncTasksSync = config.getBoolean(getBasePath() + ".run-async-tasks-sync", runAsyncTasksSync); - runAsyncTasksSync = enabled && runAsyncTasksSync; + asyncUnsafeReadHandling = config.getString(getBasePath() + ".async-unsafe-read-handling", asyncUnsafeReadHandling).toUpperCase(); + + if (!asyncUnsafeReadHandling.equals("STRICT") && !asyncUnsafeReadHandling.equals("BUFFERED") && !asyncUnsafeReadHandling.equals("DISABLED")) { + LeafConfig.LOGGER.warn("Invalid value for {}.async-unsafe-read-handling: {}, fallback to STRICT.", getBasePath(), asyncUnsafeReadHandling); + asyncUnsafeReadHandling = "STRICT"; + } + if (!enabled) { + asyncUnsafeReadHandling = "DISABLED"; + } + + runAsyncTasksSync = config.getBoolean(getBasePath() + ".run-async-tasks-sync", false); // Default to false now + if (runAsyncTasksSync) { + LeafConfig.LOGGER.warn("The setting '{}.run-async-tasks-sync' is deprecated. Use 'async-unsafe-read-handling: STRICT' for similar safety checks or 'BUFFERED' for buffered reads.", getBasePath()); + } if (enabled) { LeafConfig.LOGGER.info("Using {} threads for Parallel World Ticking", threads); } + + runAsyncTasksSync = enabled && runAsyncTasksSync; // Auto-disable if main feature is off } } diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizedPoweredRails.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizedPoweredRails.java new file mode 100644 index 00000000..b71f3702 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/OptimizedPoweredRails.java @@ -0,0 +1,18 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class OptimizedPoweredRails extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName() + ".optimized-powered-rails"; + } + + public static boolean enabled = false; + + @Override + public void onLoaded() { + enabled = config().getBoolean(getBasePath(), enabled); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceArrayMap.java b/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceArrayMap.java index 9d4619c2..3464700c 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceArrayMap.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceArrayMap.java @@ -11,7 +11,7 @@ import java.util.*; import java.util.AbstractMap.SimpleEntry; // fast array backend map with O(1) get & put & remove -public class AttributeInstanceArrayMap implements Map, AttributeInstance>, Cloneable { +public final class AttributeInstanceArrayMap implements Map, AttributeInstance>, Cloneable { private int size = 0; private transient AttributeInstance[] a = new AttributeInstance[32]; @@ -46,17 +46,17 @@ public class AttributeInstanceArrayMap implements Map, Attribu } @Override - public final int size() { + public int size() { return size; } @Override - public final boolean isEmpty() { + public boolean isEmpty() { return size == 0; } @Override - public final boolean containsKey(Object key) { + public boolean containsKey(Object key) { if (key instanceof Holder holder && holder.value() instanceof Attribute attribute) { int uid = attribute.uid; return uid >= 0 && uid < a.length && a[uid] != null; @@ -65,22 +65,22 @@ public class AttributeInstanceArrayMap implements Map, Attribu } @Override - public final boolean containsValue(Object value) { - for (final AttributeInstance instance : a) { - if (Objects.equals(value, instance)) { - return true; - } - } - return false; + public boolean containsValue(Object value) { + return value instanceof AttributeInstance val && Objects.equals(getInstance(val.getAttribute().value().uid), val); } @Override - public final AttributeInstance get(Object key) { + public AttributeInstance get(Object key) { return key instanceof Holder holder && holder.value() instanceof Attribute attribute ? a[attribute.uid] : null; } + @Nullable + public AttributeInstance getInstance(int key) { + return a[key]; + } + @Override - public final AttributeInstance put(@NotNull Holder key, AttributeInstance value) { + public AttributeInstance put(@NotNull Holder key, AttributeInstance value) { int uid = key.value().uid; AttributeInstance prev = a[uid]; setByIndex(uid, value); @@ -88,7 +88,7 @@ public class AttributeInstanceArrayMap implements Map, Attribu } @Override - public final AttributeInstance remove(Object key) { + public AttributeInstance remove(Object key) { if (!(key instanceof Holder holder) || !(holder.value() instanceof Attribute attribute)) return null; int uid = attribute.uid; AttributeInstance prev = a[uid]; @@ -97,7 +97,7 @@ public class AttributeInstanceArrayMap implements Map, Attribu } @Override - public final void putAll(@NotNull Map, ? extends AttributeInstance> m) { + public void putAll(@NotNull Map, ? extends AttributeInstance> m) { for (AttributeInstance e : m.values()) { if (e != null) { setByIndex(e.getAttribute().value().uid, e); @@ -106,13 +106,13 @@ public class AttributeInstanceArrayMap implements Map, Attribu } @Override - public final void clear() { + public void clear() { Arrays.fill(a, null); size = 0; } @Override - public final @NotNull Set> keySet() { + public @NotNull Set> keySet() { if (keys == null) { keys = new KeySet(); } @@ -120,7 +120,7 @@ public class AttributeInstanceArrayMap implements Map, Attribu } @Override - public final @NotNull Collection values() { + public @NotNull Collection values() { if (values == null) { values = new Values(); } @@ -128,7 +128,7 @@ public class AttributeInstanceArrayMap implements Map, Attribu } @Override - public final @NotNull Set, AttributeInstance>> entrySet() { + public @NotNull Set, AttributeInstance>> entrySet() { if (entries == null) { entries = new EntrySet(); } @@ -136,13 +136,23 @@ public class AttributeInstanceArrayMap implements Map, Attribu } @Override - public final boolean equals(Object o) { - if (!(o instanceof AttributeInstanceArrayMap that)) return false; - return size == that.size && Arrays.equals(a, that.a); + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof Map s)) return false; + if (s.size() != size()) return false; + if (o instanceof AttributeInstanceArrayMap that) { + return Arrays.equals(a, that.a); + } + for (Entry e : s.entrySet()) { + if (!Objects.equals(get(e.getKey()), e.getValue())) { + return false; + } + } + return true; } @Override - public final int hashCode() { + public int hashCode() { return Arrays.hashCode(a); } @@ -192,7 +202,7 @@ public class AttributeInstanceArrayMap implements Map, Attribu if (!hasNext()) throw new NoSuchElementException(); currentIndex = nextIndex; nextIndex = findNextOccupied(nextIndex + 1); - return BuiltInRegistries.ATTRIBUTE.asHolderIdMap().byIdOrThrow(currentIndex); + return BuiltInRegistries.ATTRIBUTE.get(currentIndex).orElseThrow(); } @Override @@ -279,7 +289,7 @@ public class AttributeInstanceArrayMap implements Map, Attribu public Entry, AttributeInstance> next() { if (!hasNext()) throw new NoSuchElementException(); currentIndex = nextIndex; - Holder key = BuiltInRegistries.ATTRIBUTE.asHolderIdMap().byIdOrThrow(nextIndex); + Holder key = BuiltInRegistries.ATTRIBUTE.get(nextIndex).orElseThrow(); AttributeInstance value = a[nextIndex]; nextIndex = findNextOccupied(nextIndex + 1); return new SimpleEntry<>(key, value) { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceSet.java b/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceSet.java new file mode 100644 index 00000000..5b1f32da --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/map/AttributeInstanceSet.java @@ -0,0 +1,113 @@ +package org.dreeam.leaf.util.map; + +import it.unimi.dsi.fastutil.ints.IntArraySet; +import it.unimi.dsi.fastutil.ints.IntSet; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Array; +import java.util.*; + +public final class AttributeInstanceSet extends AbstractCollection implements Set { + public final IntSet inner; + public final AttributeInstanceArrayMap map; + + public AttributeInstanceSet(AttributeInstanceArrayMap map) { + this.map = map; + inner = new IntArraySet(); + } + + @Override + public boolean add(AttributeInstance instance) { + return inner.add(instance.getAttribute().value().uid); + } + + @Override + public boolean remove(Object o) { + return o instanceof AttributeInstance instance && inner.remove(instance.getAttribute().value().uid); + } + + @Override + public @NotNull Iterator iterator() { + return new CloneIterator(inner.toIntArray(), map); + } + + @Override + public int size() { + return inner.size(); + } + + @Override + public boolean isEmpty() { + return inner.isEmpty(); + } + + @Override + public void clear() { + inner.clear(); + } + + @Override + public boolean contains(Object o) { + if (o instanceof AttributeInstance instance) { + return inner.contains(instance.getAttribute().value().uid); + } + return false; + } + + @Override + public AttributeInstance @NotNull [] toArray() { + int[] innerClone = inner.toIntArray(); + AttributeInstance[] arr = new AttributeInstance[innerClone.length]; + for (int i = 0; i < arr.length; i++) { + arr[i] = map.getInstance(innerClone[i]); + } + return arr; + } + + @SuppressWarnings({"unchecked"}) + @Override + public T @NotNull [] toArray(T[] a) { + if (a == null || (a.getClass() == AttributeInstance[].class && a.length == 0)) { + return (T[]) toArray(); + } + if (a.length < size()) { + a = (T[]) Array.newInstance(a.getClass().getComponentType(), size()); + } + System.arraycopy((T[]) toArray(), 0, a, 0, size()); + if (a.length > size()) { + a[size()] = null; + } + return a; + } + + static class CloneIterator implements Iterator { + private final int[] array; + private int index = 0; + private final AttributeInstanceArrayMap map; + + CloneIterator(int[] array, AttributeInstanceArrayMap map) { + this.array = array; + this.map = map; + } + + @Override + public boolean hasNext() { + return index < array.length; + } + + @Override + public AttributeInstance next() { + if (!hasNext()) throw new NoSuchElementException(); + return map.getInstance(array[index++]); + } + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof Set s)) return false; + if (s.size() != size()) return false; + return containsAll(s); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/biome/PositionalBiomeGetter.java b/leaf-server/src/main/java/org/dreeam/leaf/world/biome/PositionalBiomeGetter.java similarity index 96% rename from leaf-server/src/main/java/org/dreeam/leaf/util/biome/PositionalBiomeGetter.java rename to leaf-server/src/main/java/org/dreeam/leaf/world/biome/PositionalBiomeGetter.java index 042dde39..a47e7348 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/util/biome/PositionalBiomeGetter.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/world/biome/PositionalBiomeGetter.java @@ -1,4 +1,4 @@ -package org.dreeam.leaf.util.biome; +package org.dreeam.leaf.world.biome; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; diff --git a/leaf-server/src/main/java/org/dreeam/leaf/world/block/OptimizedPoweredRails.java b/leaf-server/src/main/java/org/dreeam/leaf/world/block/OptimizedPoweredRails.java new file mode 100644 index 00000000..f888ff94 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/world/block/OptimizedPoweredRails.java @@ -0,0 +1,333 @@ +package org.dreeam.leaf.world.block; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.PoweredRailBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.RailShape; + +import java.util.HashMap; + +import static net.minecraft.world.level.block.Block.*; +import static net.minecraft.world.level.block.PoweredRailBlock.POWERED; +import static net.minecraft.world.level.block.PoweredRailBlock.SHAPE; + +public class OptimizedPoweredRails { + + private static final Direction[] EAST_WEST_DIR = new Direction[]{Direction.WEST, Direction.EAST}; + private static final Direction[] NORTH_SOUTH_DIR = new Direction[]{Direction.SOUTH, Direction.NORTH}; + + private static final int UPDATE_FORCE_PLACE = UPDATE_MOVE_BY_PISTON | UPDATE_KNOWN_SHAPE | UPDATE_CLIENTS; + + private static int RAIL_POWER_LIMIT = 8; + + private static void giveShapeUpdate(Level level, BlockState state, BlockPos pos, BlockPos fromPos, Direction direction) { + BlockState oldState = level.getBlockState(pos); + Block.updateOrDestroy( + oldState, + oldState.updateShape(level, level, pos, direction.getOpposite(), fromPos, state, level.random), + level, + pos, + UPDATE_CLIENTS & -34, + 0 + ); + } + + public static int getRailPowerLimit() { + return RAIL_POWER_LIMIT; + } + + public static void setRailPowerLimit(int powerLimit) { + RAIL_POWER_LIMIT = powerLimit; + } + + public static void updateState(PoweredRailBlock self, BlockState state, Level level, BlockPos pos) { + boolean shouldBePowered = level.hasNeighborSignal(pos) || + self.findPoweredRailSignal(level, pos, state, true, 0) || + self.findPoweredRailSignal(level, pos, state, false, 0); + if (shouldBePowered != state.getValue(POWERED)) { + RailShape railShape = state.getValue(SHAPE); + if (railShape.isSlope()) { + level.setBlock(pos, state.setValue(POWERED, shouldBePowered), 3); + level.updateNeighborsAtExceptFromFacing(pos.below(), self, Direction.UP, null); + level.updateNeighborsAtExceptFromFacing(pos.above(), self, Direction.DOWN, null); // isSlope + } else if (shouldBePowered) { + powerLane(self, level, pos, state, railShape); + } else { + dePowerLane(self, level, pos, state, railShape); + } + } + } + + private static boolean findPoweredRailSignalFaster(PoweredRailBlock self, Level world, BlockPos pos, + boolean bl, int distance, RailShape shape, + HashMap checkedPos) { + BlockState blockState = world.getBlockState(pos); + boolean speedCheck = checkedPos.containsKey(pos) && checkedPos.get(pos); + if (speedCheck) { + return world.hasNeighborSignal(pos) || + findPoweredRailSignalFaster(self, world, pos, blockState, bl, distance + 1, checkedPos); + } else { + if (blockState.is(self)) { + RailShape railShape = blockState.getValue(SHAPE); + if (shape == RailShape.EAST_WEST && ( + railShape == RailShape.NORTH_SOUTH || + railShape == RailShape.ASCENDING_NORTH || + railShape == RailShape.ASCENDING_SOUTH + ) || shape == RailShape.NORTH_SOUTH && ( + railShape == RailShape.EAST_WEST || + railShape == RailShape.ASCENDING_EAST || + railShape == RailShape.ASCENDING_WEST + )) { + return false; + } else if (blockState.getValue(POWERED)) { + return world.hasNeighborSignal(pos) || + findPoweredRailSignalFaster(self, world, pos, blockState, bl, distance + 1, checkedPos); + } else { + return false; + } + } + return false; + } + } + + private static boolean findPoweredRailSignalFaster(PoweredRailBlock self, Level level, + BlockPos pos, BlockState state, boolean bl, int distance, + HashMap checkedPos) { + if (distance >= RAIL_POWER_LIMIT - 1) return false; + int i = pos.getX(); + int j = pos.getY(); + int k = pos.getZ(); + boolean bl2 = true; + RailShape railShape = state.getValue(SHAPE); + switch (railShape.ordinal()) { + case 0 -> { + if (bl) ++k; + else --k; + } + case 1 -> { + if (bl) --i; + else ++i; + } + case 2 -> { + if (bl) { + --i; + } else { + ++i; + ++j; + bl2 = false; + } + railShape = RailShape.EAST_WEST; + } + case 3 -> { + if (bl) { + --i; + ++j; + bl2 = false; + } else { + ++i; + } + railShape = RailShape.EAST_WEST; + } + case 4 -> { + if (bl) { + ++k; + } else { + --k; + ++j; + bl2 = false; + } + railShape = RailShape.NORTH_SOUTH; + } + case 5 -> { + if (bl) { + ++k; + ++j; + bl2 = false; + } else { + --k; + } + railShape = RailShape.NORTH_SOUTH; + } + } + return findPoweredRailSignalFaster( + self, level, new BlockPos(i, j, k), + bl, distance, railShape, checkedPos + ) || + (bl2 && findPoweredRailSignalFaster( + self, level, new BlockPos(i, j - 1, k), + bl, distance, railShape, checkedPos + )); + } + + private static void powerLane(PoweredRailBlock self, Level world, BlockPos pos, + BlockState mainState, RailShape railShape) { + world.setBlock(pos, mainState.setValue(POWERED, true), UPDATE_FORCE_PLACE); + HashMap checkedPos = new HashMap<>(); + checkedPos.put(pos, true); + int[] count = new int[2]; + if (railShape == RailShape.NORTH_SOUTH) { // Order: +z, -z + for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) { + setRailPositionsPower(self, world, pos, checkedPos, count, i, NORTH_SOUTH_DIR[i]); + } + updateRails(self, false, world, pos, mainState, count); + } else if (railShape == RailShape.EAST_WEST) { // Order: -x, +x + for (int i = 0; i < EAST_WEST_DIR.length; ++i) { + setRailPositionsPower(self, world, pos, checkedPos, count, i, EAST_WEST_DIR[i]); + } + updateRails(self, true, world, pos, mainState, count); + } + } + + private static void dePowerLane(PoweredRailBlock self, Level world, BlockPos pos, + BlockState mainState, RailShape railShape) { + world.setBlock(pos, mainState.setValue(POWERED, false), UPDATE_FORCE_PLACE); + int[] count = new int[2]; + if (railShape == RailShape.NORTH_SOUTH) { // Order: +z, -z + for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) { + setRailPositionsDePower(self, world, pos, count, i, NORTH_SOUTH_DIR[i]); + } + updateRails(self, false, world, pos, mainState, count); + } else if (railShape == RailShape.EAST_WEST) { // Order: -x, +x + for (int i = 0; i < EAST_WEST_DIR.length; ++i) { + setRailPositionsDePower(self, world, pos, count, i, EAST_WEST_DIR[i]); + } + updateRails(self, true, world, pos, mainState, count); + } + } + + private static void setRailPositionsPower(PoweredRailBlock self, Level world, BlockPos pos, + HashMap checkedPos, int[] count, int i, Direction dir) { + for (int z = 1; z < RAIL_POWER_LIMIT; z++) { + BlockPos newPos = pos.relative(dir, z); + BlockState state = world.getBlockState(newPos); + if (checkedPos.containsKey(newPos)) { + if (!checkedPos.get(newPos)) break; + count[i]++; + } else if (!state.is(self) || state.getValue(POWERED) || !( + world.hasNeighborSignal(newPos) || + findPoweredRailSignalFaster(self, world, newPos, state, true, 0, checkedPos) || + findPoweredRailSignalFaster(self, world, newPos, state, false, 0, checkedPos) + )) { + checkedPos.put(newPos, false); + break; + } else { + checkedPos.put(newPos, true); + world.setBlock(newPos, state.setValue(POWERED, true), UPDATE_FORCE_PLACE); + count[i]++; + } + } + } + + private static void setRailPositionsDePower(PoweredRailBlock self, Level world, BlockPos pos, + int[] count, int i, Direction dir) { + for (int z = 1; z < RAIL_POWER_LIMIT; z++) { + BlockPos newPos = pos.relative(dir, z); + BlockState state = world.getBlockState(newPos); + if (!state.is(self) || !state.getValue(POWERED) || world.hasNeighborSignal(newPos) || + self.findPoweredRailSignal(world, newPos, state, true, 0) || + self.findPoweredRailSignal(world, newPos, state, false, 0)) break; + world.setBlock(newPos, state.setValue(POWERED, false), UPDATE_FORCE_PLACE); + count[i]++; + } + } + + private static void shapeUpdateEnd(PoweredRailBlock self, Level world, BlockPos pos, BlockState mainState, + int endPos, Direction direction, int currentPos, BlockPos blockPos) { + if (currentPos == endPos) { + BlockPos newPos = pos.relative(direction, currentPos + 1); + giveShapeUpdate(world, mainState, newPos, pos, direction); + BlockState state = world.getBlockState(blockPos); + if (state.is(self) && state.getValue(SHAPE).isSlope()) giveShapeUpdate(world, mainState, newPos.above(), pos, direction); + } + } + + private static void neighborUpdateEnd(PoweredRailBlock self, Level world, BlockPos pos, int endPos, + Direction direction, Block block, int currentPos, BlockPos blockPos) { + if (currentPos == endPos) { + BlockPos newPos = pos.relative(direction, currentPos + 1); + world.neighborChanged(newPos, block, null); + BlockState state = world.getBlockState(blockPos); + if (state.is(self) && state.getValue(SHAPE).isSlope()) world.neighborChanged(newPos.above(), block, null); + } + } + + private static void updateRailsSectionEastWestShape(PoweredRailBlock self, Level world, BlockPos pos, + int c, BlockState mainState, Direction dir, + int[] count, int countAmt) { + BlockPos pos1 = pos.relative(dir, c); + if (c == 0 && count[1] == 0) giveShapeUpdate(world, mainState, pos1.relative(dir.getOpposite()), pos, dir.getOpposite()); + shapeUpdateEnd(self, world, pos, mainState, countAmt, dir, c, pos1); + giveShapeUpdate(world, mainState, pos1.below(), pos, Direction.DOWN); + giveShapeUpdate(world, mainState, pos1.above(), pos, Direction.UP); + giveShapeUpdate(world, mainState, pos1.north(), pos, Direction.NORTH); + giveShapeUpdate(world, mainState, pos1.south(), pos, Direction.SOUTH); + } + + private static void updateRailsSectionNorthSouthShape(PoweredRailBlock self, Level world, BlockPos pos, + int c, BlockState mainState, Direction dir, + int[] count, int countAmt) { + BlockPos pos1 = pos.relative(dir, c); + giveShapeUpdate(world, mainState, pos1.west(), pos, Direction.WEST); + giveShapeUpdate(world, mainState, pos1.east(), pos, Direction.EAST); + giveShapeUpdate(world, mainState, pos1.below(), pos, Direction.DOWN); + giveShapeUpdate(world, mainState, pos1.above(), pos, Direction.UP); + shapeUpdateEnd(self, world, pos, mainState, countAmt, dir, c, pos1); + if (c == 0 && count[1] == 0) giveShapeUpdate(world, mainState, pos1.relative(dir.getOpposite()), pos, dir.getOpposite()); + } + + private static void updateRails(PoweredRailBlock self, boolean eastWest, Level world, + BlockPos pos, BlockState mainState, int[] count) { + if (eastWest) { + for (int i = 0; i < EAST_WEST_DIR.length; ++i) { + int countAmt = count[i]; + if (i == 1 && countAmt == 0) continue; + Direction dir = EAST_WEST_DIR[i]; + Block block = mainState.getBlock(); + for (int c = countAmt; c >= i; c--) { + BlockPos p = pos.relative(dir, c); + if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()), block, null); + neighborUpdateEnd(self, world, pos, countAmt, dir, block, c, p); + world.neighborChanged(p.below(), block, null); + world.neighborChanged(p.above(), block, null); + world.neighborChanged(p.north(), block, null); + world.neighborChanged(p.south(), block, null); + BlockPos pos2 = pos.relative(dir, c).below(); + world.neighborChanged(pos2.below(), block, null); + world.neighborChanged(pos2.north(), block, null); + world.neighborChanged(pos2.south(), block, null); + if (c == countAmt) world.neighborChanged(pos.relative(dir, c + 1).below(), block, null); + if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()).below(), block, null); + } + for (int c = countAmt; c >= i; c--) + updateRailsSectionEastWestShape(self, world, pos, c, mainState, dir, count, countAmt); + } + } else { + for (int i = 0; i < NORTH_SOUTH_DIR.length; ++i) { + int countAmt = count[i]; + if (i == 1 && countAmt == 0) continue; + Direction dir = NORTH_SOUTH_DIR[i]; + Block block = mainState.getBlock(); + for (int c = countAmt; c >= i; c--) { + BlockPos p = pos.relative(dir, c); + world.neighborChanged(p.west(), block, null); + world.neighborChanged(p.east(), block, null); + world.neighborChanged(p.below(), block, null); + world.neighborChanged(p.above(), block, null); + neighborUpdateEnd(self, world, pos, countAmt, dir, block, c, p); + if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()), block, null); + BlockPos pos2 = pos.relative(dir, c).below(); + world.neighborChanged(pos2.west(), block, null); + world.neighborChanged(pos2.east(), block, null); + world.neighborChanged(pos2.below(), block, null); + if (c == countAmt) world.neighborChanged(pos.relative(dir, c + 1).below(), block, null); + if (c == 0 && count[1] == 0) world.neighborChanged(p.relative(dir.getOpposite()).below(), block, null); + } + for (int c = countAmt; c >= i; c--) + updateRailsSectionNorthSouthShape(self, world, pos, c, mainState, dir, count, countAmt); + } + } + } +} diff --git a/todos.md b/todos.md index 42ecc57f..4f35294c 100644 --- a/todos.md +++ b/todos.md @@ -13,4 +13,4 @@ - [ ] Check Purpur's Projectile offset config, in BowItem shoot - [ ] Remove Gale's attribute patch - [ ] Check SparklyPaper's mapitem update skip -- [ ] Update from Leaf 1.21.4 (curr commit: `a022d84c5b5b52f7e8a62f6bff774d0c23176ed5`) +- [ ] Update from Leaf 1.21.4 (curr commit: `12711630d4ca8788366f8abd47275c4c262ff13f`)