diff --git a/leaf-server/minecraft-patches/features/0141-SparklyPaper-Parallel-world-ticking.patch b/leaf-server/minecraft-patches/features/0141-SparklyPaper-Parallel-world-ticking.patch new file mode 100644 index 00000000..19396832 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0141-SparklyPaper-Parallel-world-ticking.patch @@ -0,0 +1,1367 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Altiami +Date: Wed, 5 Mar 2025 13:13:24 -0800 +Subject: [PATCH] SparklyPaper: Parallel world ticking + + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +index d6a30d6735d24f24a8108b6a5d15725587bb662a..1015344712ef6d13622348871418ef3f6efd8045 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +@@ -40,6 +40,7 @@ import net.minecraft.util.SortedArraySet; + import net.minecraft.util.Unit; + import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.chunk.LevelChunk; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + import org.slf4j.Logger; + import java.io.IOException; + import java.text.DecimalFormat; +@@ -1050,7 +1051,7 @@ public final class ChunkHolderManager { + if (changedFullStatus.isEmpty()) { + return; + } +- if (!TickThread.isTickThread()) { ++ if (SparklyPaperParallelWorldTicking.enabled && !TickThread.isTickThreadFor(world) || !TickThread.isTickThread()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (make configurable) + this.taskScheduler.scheduleChunkTask(() -> { + final ArrayDeque pendingFullLoadUpdate = ChunkHolderManager.this.pendingFullLoadUpdate; + for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { +@@ -1076,7 +1077,12 @@ public final class ChunkHolderManager { + + // note: never call while inside the chunk system, this will absolutely break everything + public void processUnloads() { +- TickThread.ensureTickThread("Cannot unload chunks off-main"); ++ // Leaf start - SparklyPaper parallel world ticking mod (make configurable) ++ if (SparklyPaperParallelWorldTicking.enabled) ++ TickThread.ensureTickThread(world, "Cannot unload chunks off-main"); // SparklyPaper - parallel world ticking ++ else ++ TickThread.ensureTickThread("Cannot unload chunks off-main"); ++ // Leaf end + + if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { + throw new IllegalStateException("Cannot unload chunks recursively"); +@@ -1358,7 +1364,7 @@ public final class ChunkHolderManager { + + List changedFullStatus = null; + +- final boolean isTickThread = TickThread.isTickThread(); ++ final boolean isTickThread = SparklyPaperParallelWorldTicking.enabled && TickThread.isTickThreadFor(world) || TickThread.isTickThread(); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (make configurable) + + boolean ret = false; + final boolean canProcessFullUpdates = processFullUpdates & isTickThread; +diff --git a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +index 4a881636ba21fae9e50950bbba2b4321b71d35ab..ef9b8e9b7a6156bc65a99a65da196c4acc6f12bb 100644 +--- a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +@@ -46,7 +46,7 @@ public class BoatDispenseItemBehavior extends DefaultDispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d1, d2 + d4, d3)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +index bd5bbc7e55c6bea77991fe5a3c0c2580313d16c5..bc43ffda879b24b1467443aa86b68c28463ef19e 100644 +--- a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +@@ -78,7 +78,7 @@ public class DefaultDispenseItemBehavior implements DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(itemEntity.getDeltaMovement())); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + level.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java +index f576449e8bc6fd92963cbe3954b0c853a02def3c..7f93485c1e931de04f2609a7aab3ecf0fda2ba0a 100644 +--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -89,7 +89,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -147,7 +147,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -201,7 +201,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); + + org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), entitiesOfClass.get(0).getBukkitLivingEntity()); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + world.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -251,7 +251,7 @@ public interface DispenseItemBehavior { + org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, blockSource.pos()); + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleCopy); + org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), abstractChestedHorse.getBukkitLivingEntity()); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + world.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -329,7 +329,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(x, y, z)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + level.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -389,7 +389,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + levelAccessor.getMinecraftWorld().getCraftServer().getPluginManager().callEvent(event); + } + +@@ -425,7 +425,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -482,7 +482,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + level.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -510,8 +510,8 @@ public interface DispenseItemBehavior { + // CraftBukkit start + level.captureTreeGeneration = false; + if (level.capturedBlockStates.size() > 0) { +- org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; +- net.minecraft.world.level.block.SaplingBlock.treeType = null; ++ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.getTreeTypeRT(); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) ++ net.minecraft.world.level.block.SaplingBlock.setTreeTypeRT(null); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(blockPos, level.getWorld()); + List blocks = new java.util.ArrayList<>(level.capturedBlockStates.values()); + level.capturedBlockStates.clear(); +@@ -548,7 +548,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(singleItemStack); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) blockPos.getX() + 0.5D, (double) blockPos.getY(), (double) blockPos.getZ() + 0.5D)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + level.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -591,7 +591,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + level.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -644,7 +644,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + level.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -702,7 +702,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - only single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(blockPos)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +@@ -783,7 +783,7 @@ public interface DispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), entitiesOfClass.get(0).getBukkitLivingEntity()); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java +index b91b2f5ea6a1da0477541dc65fdfbfa57b9af475..5f43f25001156c83d715c1976d3c01ae8182bf6f 100644 +--- a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java +@@ -39,7 +39,7 @@ public class EquipmentDispenseItemBehavior extends DefaultDispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack); + + org.bukkit.event.block.BlockDispenseArmorEvent event = new org.bukkit.event.block.BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) livingEntity.getBukkitEntity()); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + world.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java b/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java +index 116395b6c00a0814922516707544a9ff26d68835..cf828a772b768f158f132c32adb8781de12cd7b7 100644 +--- a/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java +@@ -62,7 +62,7 @@ public class MinecartDispenseItemBehavior extends DefaultDispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block2, craftItem.clone(), new org.bukkit.util.Vector(vec31.x, vec31.y, vec31.z)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java b/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java +index 449d9b72ff4650961daa9d1bd25940f3914a6b12..195ba81fe7af9046fb07504768daf8f5825fe3e3 100644 +--- a/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java ++++ b/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java +@@ -32,7 +32,7 @@ public class ProjectileDispenseBehavior extends DefaultDispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack1); + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) direction.getStepX(), (double) direction.getStepY(), (double) direction.getStepZ())); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +index 626e9feb6a6e7a2cbc7c63e30ba4fb6b923e85c7..4814dd4340139020aae96f0286c4ac9ac0fb933e 100644 +--- a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +@@ -25,7 +25,7 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior { + org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, blockSource.pos()); + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item); // Paper - ignore stack size on damageable items + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + serverLevel.getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java b/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java +index 5ab2c8333178335515e619b87ae420f948c83bd1..3b378ef89bdf64190f21003ee3dc0f8723375418 100644 +--- a/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java ++++ b/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java +@@ -27,7 +27,7 @@ public class ShulkerBoxDispenseBehavior extends OptionalDispenseItemBehavior { + org.bukkit.craftbukkit.inventory.CraftItemStack craftItem = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(item.copyWithCount(1)); // Paper - single item in event + + org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockPos.getX(), blockPos.getY(), blockPos.getZ())); +- if (!DispenserBlock.eventFired) { ++ if (!DispenserBlock.getEventFiredTL()) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + blockSource.level().getCraftServer().getPluginManager().callEvent(event); + } + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 3597f7dd2d71fe136604518985e3d14461a6aad4..dd1c55e983bc1ddc9a77a0b825b78eba62c201ec 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1,5 +1,6 @@ + package net.minecraft.server; + ++import ca.spottedleaf.moonrise.common.util.TickThread; + import com.google.common.base.Preconditions; + import com.google.common.base.Splitter; + import com.google.common.collect.ImmutableList; +@@ -160,6 +161,7 @@ import net.minecraft.world.level.storage.ServerLevelData; + import net.minecraft.world.level.storage.WorldData; + import net.minecraft.world.phys.Vec2; + import net.minecraft.world.phys.Vec3; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + import org.slf4j.Logger; + + public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements ServerInfo, ChunkIOErrorReporter, CommandSource, ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer { // Paper - rewrite chunk system +@@ -291,6 +293,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop entitiesWithScheduledTasks = java.util.concurrent.ConcurrentHashMap.newKeySet(); // SparklyPaper - skip EntityScheduler's executeTick checks if there isn't any tasks to be run (concurrent because plugins may schedule tasks async) ++ public java.util.concurrent.Semaphore serverLevelTickingSemaphore = null; // SparklyPaper - parallel world ticking + + public static S spin(Function threadFunction) { + ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system +@@ -323,24 +326,34 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop serverPlayer1.connection.suspendFlushing()); + this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit +@@ -1733,28 +1757,50 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent +- serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent +- serverLevel.updateLagCompensationTick(); // Paper - lag compensation +- net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = serverLevel.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers +- serverLevel.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur - Ridables +- /* Drop global time updates +- if (this.tickCount % 20 == 0) { +- this.synchronizeTime(serverLevel); +- } +- // CraftBukkit end */ ++ // SparklyPaper start - parallel world ticking ++ java.util.ArrayDeque> tasks = new java.util.ArrayDeque<>(); ++ try { ++ for (ServerLevel serverLevel : this.getAllLevels()) { ++ serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent ++ serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent ++ serverLevel.updateLagCompensationTick(); // Paper - lag compensation ++ net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = serverLevel.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers ++ serverLevel.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur - Ridables ++ /* Drop global time updates ++ if (this.tickCount % 20 == 0) { ++ this.synchronizeTime(serverLevel); ++ } ++ // CraftBukkit end */ + +- try { +- serverLevel.tick(hasTimeLeft); +- } catch (Throwable var7) { +- CrashReport crashReport = CrashReport.forThrowable(var7, "Exception ticking world"); +- serverLevel.fillReportDetails(crashReport); +- throw new ReportedException(crashReport); ++ // Leaf start - SparklyPaper parallel world ticking mod (make configurable) ++ if (SparklyPaperParallelWorldTicking.enabled) { ++ serverLevelTickingSemaphore.acquire(); ++ tasks.add( ++ serverLevel.tickExecutor.submit(() -> { ++ ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread currentThread = (ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread) Thread.currentThread(); ++ currentThread.currentlyTickingServerLevel = serverLevel; ++ ++ try { ++ tickLevel(serverLevel, hasTimeLeft); // Leaf - SparklyPaper parallel world ticking mod (move level ticking logic out for branch convergence) ++ } finally { ++ serverLevelTickingSemaphore.release(); ++ } ++ }, serverLevel) ++ ); ++ } else ++ tickLevel(serverLevel, hasTimeLeft); ++ // Leaf end ++ ++ serverLevel.explosionDensityCache.clear(); // Paper - Optimize explosions + } + +- serverLevel.explosionDensityCache.clear(); // Paper - Optimize explosions ++ while (!tasks.isEmpty()) { ++ tasks.pop().get(); ++ } ++ } catch (java.lang.InterruptedException | java.util.concurrent.ExecutionException e) { ++ throw new RuntimeException(e); // Propagate exception + } ++ // SparklyPaper end + this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked + + this.tickConnection(); +@@ -1834,6 +1880,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop, ServerLevel> oldLevels = this.levels; + Map, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels); + newLevels.remove(level.dimension()); ++ if (SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper parallel world ticking mod (make configurable) ++ level.tickExecutor.shutdown(); // SparklyPaper - parallel world ticking (We remove it in here instead of ServerLevel.close() because ServerLevel.close() is never called!) + this.levels = Collections.unmodifiableMap(newLevels); + } + // CraftBukkit end +diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java +index d4048661575ebfaf128ba25da365843774364e0e..64f8824c378d92dbeab2558e6fdc5648e34ed98e 100644 +--- a/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -57,6 +57,7 @@ import net.minecraft.world.level.GameType; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.block.entity.SkullBlockEntity; + import net.minecraft.world.level.storage.LevelStorageSource; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + import org.slf4j.Logger; + + public class DedicatedServer extends MinecraftServer implements ServerInterface { +@@ -249,6 +250,12 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + } + org.purpurmc.purpur.PurpurConfig.registerCommands(); + */// Purpur end - Purpur config files // Purpur - Configurable void damage height and damage ++ // Leaf start - SparklyPaper parallel world ticking mod (make configurable) ++ if (SparklyPaperParallelWorldTicking.enabled) { ++ serverLevelTickingSemaphore = new java.util.concurrent.Semaphore(SparklyPaperParallelWorldTicking.threads); // SparklyPaper - parallel world ticking ++ DedicatedServer.LOGGER.info("Using " + serverLevelTickingSemaphore.availablePermits() + " permits for parallel world ticking"); // SparklyPaper - parallel world ticking ++ } ++ // Leaf end + com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now + + // Gale start - Pufferfish - SIMD support +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 5da07e22ef9dac7baca9d8450b7eae3f6fa141b1..3c0975e419b8ce58371e0cfae07abdad498bf5df 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -166,6 +166,7 @@ import net.minecraft.world.phys.shapes.BooleanOp; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; + import net.minecraft.world.ticks.LevelTicks; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + import org.slf4j.Logger; + + public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLevel, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevelReader, ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel { // Paper - rewrite chunk system // Paper - chunk tick iteration +@@ -182,7 +183,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + private final MinecraftServer server; + public final net.minecraft.world.level.storage.PrimaryLevelData serverLevelData; // CraftBukkit - type + private int lastSpawnChunkRadius; +- final EntityTickList entityTickList = new EntityTickList(); ++ final EntityTickList entityTickList = new EntityTickList(this); // SparklyPaper - parallel world ticking + // Paper - rewrite chunk system + private final GameEventDispatcher gameEventDispatcher; + public boolean noSave; +@@ -208,6 +209,7 @@ 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 + + // CraftBukkit start + public final LevelStorageSource.LevelStorageAccess levelStorageAccess; +@@ -703,6 +705,7 @@ 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 + } + + // Paper start +@@ -1286,11 +1289,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + if (fluidState.is(fluid)) { + fluidState.tick(this, pos, blockState); + } +- // Paper start - rewrite chunk system +- if ((++this.tickedBlocksOrFluids & 7L) != 0L) { ++ // Paper start - rewrite chunk system // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Leaf start - SparklyPaper parallel world ticking mod (do not bother with condition work / make configurable) ++ ++this.tickedBlocksOrFluids; ++ if (!SparklyPaperParallelWorldTicking.enabled && (this.tickedBlocksOrFluids & 7L) != 0L) { + ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); + } +- // Paper end - rewrite chunk system ++ // Paper end // SparklyPaper end // Leaf end + + } + +@@ -1299,11 +1303,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + if (blockState.is(block)) { + blockState.tick(this, pos, this.random); + } +- // Paper start - rewrite chunk system +- if ((++this.tickedBlocksOrFluids & 7L) != 0L) { ++ // Paper start - rewrite chunk system // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Leaf start - SparklyPaper parallel world ticking mod (do not bother with condition work / make configurable) ++ ++this.tickedBlocksOrFluids; ++ if (!SparklyPaperParallelWorldTicking.enabled && (this.tickedBlocksOrFluids & 7L) != 0L) { + ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); + } +- // Paper end - rewrite chunk system ++ // Paper end // SparklyPaper end // Leaf end + + } + +@@ -1553,6 +1558,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + private void addPlayer(ServerPlayer player) { ++ if (SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper parallel world ticking mod (make configurable / async is no longer safe; schedule on main) ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add player off-main"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) + Entity entity = this.getEntities().get(player.getUUID()); + if (entity != null) { + LOGGER.warn("Force-added player with duplicate UUID {}", player.getUUID()); +@@ -1565,7 +1572,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + // CraftBukkit start + private boolean addEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { +- org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot ++ // Leaf start - SparklyPaper parallel world ticking mod (make configurable ++ if (SparklyPaperParallelWorldTicking.enabled) ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add entity off-main"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ else ++ org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot ++ // Leaf end + entity.generation = false; // Paper - Don't fire sync event during generation; Reset flag if it was added during a ServerLevel generation process + // Paper start - extra debug info + if (entity.valid) { +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index 523d27ae8837bc4da2f993964aa99ab91617ec01..f9900d8193dc804652bd5485fdc4ebd3ac4f667a 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -174,6 +174,7 @@ import net.minecraft.world.scores.ScoreAccess; + import net.minecraft.world.scores.ScoreHolder; + import net.minecraft.world.scores.Team; + import net.minecraft.world.scores.criteria.ObjectiveCriteria; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + import org.slf4j.Logger; + + public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patches.chunk_system.player.ChunkSystemServerPlayer { // Paper - rewrite chunk system +@@ -427,6 +428,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + return this.viewDistanceHolder; + } + // Paper end - rewrite chunk system ++ public boolean hasTickedAtLeastOnceInNewWorld = false; // SparklyPaper - parallel world ticking (fixes bug in DreamResourceReset where the inventory is opened AFTER the player has changed worlds, if you click with the quick tp torch in a chest, because the inventory is opened AFTER the player has teleported) + + public ServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) { + super(level, level.getSharedSpawnPos(), level.getSharedSpawnAngle(), gameProfile); +@@ -803,6 +805,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + + @Override + public void tick() { ++ hasTickedAtLeastOnceInNewWorld = true; // SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) + // CraftBukkit start + if (this.joining) { + this.joining = false; +@@ -1447,6 +1450,8 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + teleportTransition.postTeleportTransition().onTransition(this); + return this; + } else { ++ if (SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper parallel world ticking mod (make configurable) ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot change dimension of a player off-main, from world " + serverLevel().getWorld().getName() + " to world " + level.getWorld().getName()); // SparklyPaper - parallel world ticking (additional concurrency issues logs) + // CraftBukkit start + /* + this.isChangingDimension = true; +@@ -1818,6 +1823,12 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + return OptionalInt.empty(); + } else { + // CraftBukkit start ++ // SparklyPaper start - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) ++ if (SparklyPaperParallelWorldTicking.enabled && !hasTickedAtLeastOnceInNewWorld) { // Leaf - SparklyPaper parallel world ticking mod (make configurable) ++ MinecraftServer.LOGGER.warn("Ignoring request to open container " + abstractContainerMenu + " because we haven't ticked in the current world yet!", new Throwable()); ++ return OptionalInt.empty(); ++ } ++ // SparklyPaper end + this.containerMenu = abstractContainerMenu; // Moved up + if (!this.isImmobile()) + this.connection +@@ -1882,6 +1893,11 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + } + @Override + public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ // SparklyPaper start - parallel world ticking (debugging) ++ if (SparklyPaperParallelWorldTicking.logContainerCreationStacktraces) { ++ MinecraftServer.LOGGER.warn("Closing " + this.getBukkitEntity().getName() + " inventory that was created at", this.containerMenu.containerCreationStacktrace); ++ } ++ // SparklyPaper end + org.bukkit.craftbukkit.event.CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit + // Paper end - Inventory close reason + this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); +diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java +index e402e5a741c3864f66cf9c713f2bb83191d4607e..c48dea3a728659587e635ab317599d94e1d30547 100644 +--- a/net/minecraft/server/players/PlayerList.java ++++ b/net/minecraft/server/players/PlayerList.java +@@ -1,5 +1,6 @@ + package net.minecraft.server.players; + ++import ca.spottedleaf.moonrise.common.util.TickThread; + import com.google.common.collect.Lists; + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; +@@ -17,6 +18,9 @@ import java.util.Map; + import java.util.Optional; + import java.util.Set; + import java.util.UUID; ++import java.util.concurrent.CompletionException; ++import java.util.concurrent.ExecutionException; ++import java.util.concurrent.FutureTask; + import java.util.function.Function; + import java.util.function.Predicate; + import javax.annotation.Nullable; +@@ -99,6 +103,7 @@ import net.minecraft.world.scores.DisplaySlot; + import net.minecraft.world.scores.Objective; + import net.minecraft.world.scores.PlayerTeam; + import net.minecraft.world.scores.Team; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + import org.slf4j.Logger; + + public abstract class PlayerList { +@@ -252,6 +257,8 @@ public abstract class PlayerList { + // Leaves end - replay mod api + + public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie cookie) { ++ if (SparklyPaperParallelWorldTicking.enabled)// Leaf - SparklyPaper parallel world ticking mod (make configurable) ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot place new player off-main"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) + player.isRealPlayer = true; // Paper + player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed + GameProfile gameProfile = player.getGameProfile(); +@@ -891,6 +898,17 @@ public abstract class PlayerList { + return this.respawn(player, keepInventory, reason, eventReason, null); + } + public ServerPlayer respawn(ServerPlayer player, boolean keepInventory, Entity.RemovalReason reason, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason eventReason, org.bukkit.Location location) { ++ // Leaf start - SparklyPaper parallel world ticking mod (make configurable) ++ if (SparklyPaperParallelWorldTicking.enabled) { ++ // SparklyPaper - parallel world ticking (additional concurrency issues logs) ++ System.out.println("respawning player - current player container is " + player.containerMenu + " but their inventory is " + player.inventoryMenu); ++ if (location != null) // Leaf - THIS CAN BE NULL; see PlayerList::respawn(ServerPlayer, boolean, Entity.RemovalReason, PlayerRespawnEvent.RespawnReason) ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, from world " + player.serverLevel().getWorld().getName() + " to world " + location.getWorld().getName()); ++ else ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, respawning in world " + player.serverLevel().getWorld().getName()); ++ // SparklyPaper end ++ } ++ // Leaf end + player.stopRiding(); // CraftBukkit + this.players.remove(player); + this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot +@@ -902,6 +920,7 @@ public abstract class PlayerList { + ServerPlayer serverPlayer = player; + Level fromWorld = player.level(); + player.wonGame = false; ++ serverPlayer.hasTickedAtLeastOnceInNewWorld = false; // SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) + // CraftBukkit end + serverPlayer.connection = player.connection; + serverPlayer.restoreFrom(player, keepInventory); +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index 9bc978ca290ca772b0367e89b69fe16b502b0cd2..db9864a55f6f28aaec0054b054bdabf58f76ce0f 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -83,10 +83,15 @@ import net.minecraft.world.InteractionResult; + import net.minecraft.world.Nameable; + import net.minecraft.world.damagesource.DamageSource; + import net.minecraft.world.damagesource.DamageSources; ++import net.minecraft.world.entity.boss.enderdragon.EndCrystal; ++import net.minecraft.world.entity.item.FallingBlockEntity; + import net.minecraft.world.entity.item.ItemEntity; ++import net.minecraft.world.entity.item.PrimedTnt; + import net.minecraft.world.entity.player.Player; ++import net.minecraft.world.entity.projectile.AbstractArrow; + import net.minecraft.world.entity.projectile.Projectile; + import net.minecraft.world.entity.projectile.ProjectileDeflection; ++import net.minecraft.world.entity.projectile.ShulkerBullet; + import net.minecraft.world.entity.vehicle.AbstractBoat; + import net.minecraft.world.item.Item; + import net.minecraft.world.item.ItemStack; +@@ -131,6 +136,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; + import net.minecraft.world.scores.PlayerTeam; + import net.minecraft.world.scores.ScoreHolder; + import net.minecraft.world.scores.Team; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + import org.slf4j.Logger; + + public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, ScoreHolder, ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity, ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity { // Paper - rewrite chunk system // Paper - optimise entity tracker +@@ -866,7 +872,20 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + // CraftBukkit start + public void postTick() { + // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle +- if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities ++ if (!( ++ this instanceof ServerPlayer || ++ // Leaf start - SparklyPaper parallel world ticking mod (per Potothingi's mod: skip entities which handle portals earlier) ++ SparklyPaperParallelWorldTicking.enabled && ( ++ this instanceof EndCrystal || ++ this instanceof FallingBlockEntity || ++ this instanceof PrimedTnt || ++ this instanceof AbstractArrow || ++ this instanceof ShulkerBullet ++ ) ++ // Leaf end ++ ) ++ && this.isAlive()) // Paper - don't attempt to teleport dead entities ++ { + this.handlePortal(); + } + } +@@ -3370,14 +3389,22 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + if (this.portalProcess != null) { + if (this.portalProcess.processPortalTeleportation(serverLevel, this, this.canUsePortal(false))) { + this.setPortalCooldown(); +- TeleportTransition portalDestination = this.portalProcess.getPortalDestination(serverLevel, this); +- if (portalDestination != null) { +- ServerLevel level = portalDestination.newLevel(); +- if (this instanceof ServerPlayer // CraftBukkit - always call event for players +- || (level != null && (level.dimension() == serverLevel.dimension() || this.canTeleport(serverLevel, level)))) { // CraftBukkit +- this.teleport(portalDestination); +- } +- } ++ // TCRF SparklyPaper (Pathothingi) start - parallel world ticking ++ getBukkitEntity().taskScheduler.schedule( ++ entity -> { ++ TeleportTransition portalDestination = entity.portalProcess.getPortalDestination(serverLevel, entity); ++ if (portalDestination != null) { ++ ServerLevel level = portalDestination.newLevel(); ++ if (entity instanceof ServerPlayer // CraftBukkit - always call event for players ++ || (level != null && (level.dimension() == serverLevel.dimension() || entity.canTeleport(serverLevel, level)))) { // CraftBukkit ++ entity.teleport(portalDestination); ++ } ++ } ++ }, ++ entity -> {}, ++ 0 ++ ); ++ // TCRF SparklyPaper (Pathothingi) end + + } else if (this.portalProcess.hasExpired()) { + this.portalProcess = null; +@@ -3908,6 +3935,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + private Entity teleportCrossDimension(ServerLevel level, TeleportTransition teleportTransition) { ++ if (SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper parallel world ticking mod (make configurable) ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, "Cannot teleport entity to another world off-main, from world " + this.level.getWorld().getName() + " to world " + level.getWorld().getName()); // SparklyPaper - parallel world ticking (additional concurrency issues logs) + List passengers = this.getPassengers(); + List list = new ArrayList<>(passengers.size()); + this.ejectPassengers(); +diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java +index 0b4c4707139c9c72929799818ec1a1b25575d70e..e7d11397701c7f8c7f1602572382373de94a84b9 100644 +--- a/net/minecraft/world/entity/npc/Villager.java ++++ b/net/minecraft/world/entity/npc/Villager.java +@@ -1,5 +1,6 @@ + package net.minecraft.world.entity.npc; + ++import ca.spottedleaf.concurrentutil.util.Priority; + import com.google.common.annotations.VisibleForTesting; + import com.google.common.collect.ImmutableList; + import com.google.common.collect.ImmutableMap; +@@ -806,13 +807,16 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + this.brain.getMemory(moduleType).ifPresent(globalPos -> { + ServerLevel level = server.getLevel(globalPos.dimension()); + if (level != null) { +- PoiManager poiManager = level.getPoiManager(); +- Optional> type = poiManager.getType(globalPos.pos()); +- BiPredicate> biPredicate = POI_MEMORIES.get(moduleType); +- if (type.isPresent() && biPredicate.test(this, type.get())) { +- poiManager.release(globalPos.pos()); +- DebugPackets.sendPoiTicketCountPacket(level, globalPos.pos()); +- } ++ level.moonrise$getChunkTaskScheduler().scheduleChunkTask(0, 0, () -> { ++ PoiManager poiManager = level.getPoiManager(); ++ Optional> type = poiManager.getType(globalPos.pos()); ++ BiPredicate> biPredicate = POI_MEMORIES.get(moduleType); ++ if (type.isPresent() && biPredicate.test(this, type.get())) { ++ poiManager.release(globalPos.pos()); ++ DebugPackets.sendPoiTicketCountPacket(level, globalPos.pos()); ++ } ++ }, Priority.BLOCKING); ++ + } + }); + } +diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java +index 6033f629ac457472ad10f8e346732a596aea52d9..9386cd41657076c126c70d1582a0857a90d11278 100644 +--- a/net/minecraft/world/inventory/AbstractContainerMenu.java ++++ b/net/minecraft/world/inventory/AbstractContainerMenu.java +@@ -33,6 +33,7 @@ import net.minecraft.world.item.BundleItem; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.entity.BlockEntity; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + import org.slf4j.Logger; + + public abstract class AbstractContainerMenu { +@@ -92,8 +93,14 @@ public abstract class AbstractContainerMenu { + } + public void startOpen() {} + // CraftBukkit end ++ public Throwable containerCreationStacktrace; // SparklyPaper - parallel world ticking (debugging) + + protected AbstractContainerMenu(@Nullable MenuType menuType, int containerId) { ++ // SparklyPaper - parallel world ticking (debugging) ++ if (SparklyPaperParallelWorldTicking.logContainerCreationStacktraces) { ++ this.containerCreationStacktrace = new Throwable(); ++ } ++ // SparklyPaper end + this.menuType = menuType; + this.containerId = containerId; + } +diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java +index fd7c1e800cbd4919a1a47f6c468c8776535bd028..3a583e838f7b9019624c3bbc23154c1c5b070a30 100644 +--- a/net/minecraft/world/item/ItemStack.java ++++ b/net/minecraft/world/item/ItemStack.java +@@ -412,8 +412,8 @@ public final class ItemStack implements DataComponentHolder, net.caffeinemc.mods + if (interactionResult.consumesAction() && serverLevel.captureTreeGeneration && !serverLevel.capturedBlockStates.isEmpty()) { + serverLevel.captureTreeGeneration = false; + org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(clickedPos, serverLevel.getWorld()); +- org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; +- net.minecraft.world.level.block.SaplingBlock.treeType = null; ++ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.getTreeTypeRT(); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) ++ net.minecraft.world.level.block.SaplingBlock.setTreeTypeRT(null); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + List blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values()); + serverLevel.capturedBlockStates.clear(); + org.bukkit.event.world.StructureGrowEvent structureEvent = null; +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index 7d3163802640449b6bdaa93595518d7d0f62488b..da579769cee175cfbf342efd643efe6f87e0a822 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -99,6 +99,7 @@ import org.bukkit.craftbukkit.block.data.CraftBlockData; + import org.bukkit.craftbukkit.util.CraftSpawnCategory; + import org.bukkit.entity.SpawnCategory; + import org.bukkit.event.block.BlockPhysicsEvent; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + // CraftBukkit end + + public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel, ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter { // Paper - rewrite chunk system // Paper - optimise collisions +@@ -175,6 +176,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + public final io.papermc.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files ++ public io.papermc.paper.redstone.RedstoneWireTurbo turbo = new io.papermc.paper.redstone.RedstoneWireTurbo((net.minecraft.world.level.block.RedStoneWireBlock) net.minecraft.world.level.block.Blocks.REDSTONE_WIRE); // SparklyPaper - parallel world ticking (moved to world) + public static BlockPos lastPhysicsProblem; // Spigot + private org.spigotmc.TickLimiter entityLimiter; + private org.spigotmc.TickLimiter tileLimiter; +@@ -1153,6 +1155,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + @Override + public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft) { ++ if (SparklyPaperParallelWorldTicking.enabled) // Leaf start - SparklyPaper parallel world ticking mod (make configurable) ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, pos, "Updating block asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) + // CraftBukkit start - tree generation + if (this.captureTreeGeneration) { + // Paper start - Protect Bedrock and End Portal/Frames from being destroyed +@@ -1531,11 +1535,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + this.blockEntityTickers.markAsRemoved(this.tileTickPosition); // toRemove.add(tickingBlockEntity); // SparklyPaper - optimize block entity removals // Paper - Fix MC-117075; use removeAll + } else if (runsNormally && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) { + tickingBlockEntity.tick(); +- // Paper start - rewrite chunk system +- if ((++tickedEntities & 7) == 0) { ++ // Paper start - rewrite chunk system // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Leaf - SparklyPaper parallel world ticking mod (do not bother with condition work / make configurable) ++ ++tickedEntities; ++ if (!SparklyPaperParallelWorldTicking.enabled && (tickedEntities & 7) == 0) { + ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks(); + } +- // Paper end - rewrite chunk system ++ // Paper end - rewrite chunk system // SparklyPaper end // Leaf end + } + } + this.blockEntityTickers.removeMarkedEntries(); // SparklyPaper - optimize block entity removals +@@ -1555,7 +1560,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Gale - Airplane - remove lambda from ticking guard - diff on change ServerLevel#tick + // Paper end - Prevent block entity and entity crashes + } +- this.moonrise$midTickTasks(); // Paper - rewrite chunk system // Gale - Airplane - remove lambda from ticking guard - diff on change ServerLevel#tick ++ if (!SparklyPaperParallelWorldTicking.enabled) // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Leaf start - SparklyPaper parallel world ticking mod (make configurable) ++ this.moonrise$midTickTasks(); // Paper - rewrite chunk system // Gale - Airplane - remove lambda from ticking guard - diff on change ServerLevel#tick + } + + // Paper start - Option to prevent armor stands from doing entity lookups +@@ -1698,6 +1704,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + @Nullable + public BlockEntity getBlockEntity(BlockPos pos, boolean validate) { ++ if (SparklyPaperParallelWorldTicking.enabled) // Leaf start - SparklyPaper parallel world ticking mod (make configurable) ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThreadOrAsyncThread((ServerLevel) this, "Cannot read world asynchronously"); // SparklyPaper - parallel world ticking + // Paper start - Perf: Optimize capturedTileEntities lookup + net.minecraft.world.level.block.entity.BlockEntity blockEntity; + if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(pos)) != null) { +@@ -1715,6 +1723,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + } + + public void setBlockEntity(BlockEntity blockEntity) { ++ if (SparklyPaperParallelWorldTicking.enabled) // Leaf start - SparklyPaper parallel world ticking mod (make configurable) ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel) this, "Cannot modify world asynchronously"); // SparklyPaper - parallel world ticking + BlockPos blockPos = blockEntity.getBlockPos(); + if (!this.isOutsideBuildHeight(blockPos)) { + // CraftBukkit start +@@ -1799,6 +1809,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + @Override + public List getEntities(@Nullable Entity entity, AABB boundingBox, Predicate predicate) { ++ if (SparklyPaperParallelWorldTicking.enabled) // Leaf start - SparklyPaper parallel world ticking mod (make configurable) ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) + List list = Lists.newArrayList(); + + // Paper start - rewrite chunk system +@@ -2108,8 +2120,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public abstract RecipeAccess recipeAccess(); + + public BlockPos getBlockRandomPos(int x, int y, int z, int yMask) { +- this.randValue = this.randValue * 3 + 1013904223; +- int i = this.randValue >> 2; ++ // Leaf start - SparklyPaper parallel world ticking mod (make configurable) ++ int i; ++ if (SparklyPaperParallelWorldTicking.enabled) ++ i = this.random.nextInt() >> 2; // SparklyPaper - parallel world ticking ++ else { ++ this.randValue = this.randValue * 3 + 1013904223; ++ i = this.randValue >> 2; ++ } ++ // Leaf end + return new BlockPos(x + (i & 15), y + (i >> 16 & yMask), z + (i >> 8 & 15)); + } + +diff --git a/net/minecraft/world/level/block/DispenserBlock.java b/net/minecraft/world/level/block/DispenserBlock.java +index e0a4d41e5bcf144ea4c10d6f633c3a95ed2c5aec..719ec758f96241b64f93d93d6a7013522998f3a8 100644 +--- a/net/minecraft/world/level/block/DispenserBlock.java ++++ b/net/minecraft/world/level/block/DispenserBlock.java +@@ -40,6 +40,7 @@ import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.redstone.Orientation; + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.Vec3; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + import org.slf4j.Logger; + + public class DispenserBlock extends BaseEntityBlock { +@@ -51,6 +52,26 @@ public class DispenserBlock extends BaseEntityBlock { + public static final Map DISPENSER_REGISTRY = new IdentityHashMap<>(); + private static final int TRIGGER_DURATION = 4; + public static boolean eventFired = false; // CraftBukkit ++ public static ThreadLocal eventFiredTL = ThreadLocal.withInitial(() -> Boolean.FALSE); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (distinguish name) ++ ++ // Leaf - SparklyPaper parallel world ticking mod ++ // refer to original field in case plugins attempt to modify it ++ public static boolean getEventFiredTL() { ++ if (SparklyPaperParallelWorldTicking.enabled && eventFiredTL.get()) ++ return true; ++ synchronized (DispenserBlock.class) { ++ return eventFired; ++ } ++ } ++ ++ // update original field in case plugins attempt to access it ++ public static void setEventFiredTL(boolean value) { ++ if (SparklyPaperParallelWorldTicking.enabled) ++ eventFiredTL.set(value); ++ synchronized (DispenserBlock.class) { ++ eventFired = value; ++ } ++ } + + @Override + public MapCodec codec() { +@@ -96,7 +117,7 @@ public class DispenserBlock extends BaseEntityBlock { + DispenseItemBehavior dispenseMethod = this.getDispenseMethod(level, item); + if (dispenseMethod != DispenseItemBehavior.NOOP) { + if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(level, pos, item, randomSlot)) return; // Paper - Add BlockPreDispenseEvent +- DispenserBlock.eventFired = false; // CraftBukkit - reset event status ++ DispenserBlock.setEventFiredTL(Boolean.FALSE); // CraftBukkit - reset event status // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + dispenserBlockEntity.setItem(randomSlot, dispenseMethod.dispense(blockSource, item)); + } + } +diff --git a/net/minecraft/world/level/block/FungusBlock.java b/net/minecraft/world/level/block/FungusBlock.java +index 85f0eac75784565c658c5178c544f969db3d6f54..70084510fd591e1c3221f09d67f622201b29a54a 100644 +--- a/net/minecraft/world/level/block/FungusBlock.java ++++ b/net/minecraft/world/level/block/FungusBlock.java +@@ -76,9 +76,9 @@ public class FungusBlock extends BushBlock implements BonemealableBlock { + // CraftBukkit start + .map((value) -> { + if (this == Blocks.WARPED_FUNGUS) { +- SaplingBlock.treeType = org.bukkit.TreeType.WARPED_FUNGUS; ++ SaplingBlock.setTreeTypeRT(org.bukkit.TreeType.WARPED_FUNGUS); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + } else if (this == Blocks.CRIMSON_FUNGUS) { +- SaplingBlock.treeType = org.bukkit.TreeType.CRIMSON_FUNGUS; ++ SaplingBlock.setTreeTypeRT(org.bukkit.TreeType.CRIMSON_FUNGUS); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + } + return value; + }) +diff --git a/net/minecraft/world/level/block/MushroomBlock.java b/net/minecraft/world/level/block/MushroomBlock.java +index 904369f4d7db41026183f2de7c96c2f0f4dc204d..7b1815c498be07f4b1365736791063125675eab2 100644 +--- a/net/minecraft/world/level/block/MushroomBlock.java ++++ b/net/minecraft/world/level/block/MushroomBlock.java +@@ -94,7 +94,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock { + return false; + } else { + level.removeBlock(pos, false); +- SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM; // CraftBukkit ++ SaplingBlock.setTreeTypeRT((this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM); // CraftBukkit // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + if (optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos)) { + return true; + } else { +diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java +index 12c9d60314c99fb65e640d255a2d0c6b7790ad4d..10a969b6901e47f71a4ba2e5543822ef9c87a165 100644 +--- a/net/minecraft/world/level/block/RedStoneWireBlock.java ++++ b/net/minecraft/world/level/block/RedStoneWireBlock.java +@@ -37,6 +37,7 @@ import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.Shapes; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + + public class RedStoneWireBlock extends Block { + public static final MapCodec CODEC = simpleCodec(RedStoneWireBlock::new); +@@ -308,7 +309,12 @@ public class RedStoneWireBlock extends Block { + if (orientation != null) { + source = pos.relative(orientation.getFront().getOpposite()); + } +- turbo.updateSurroundingRedstone(worldIn, pos, state, source); ++ // Leaf - SparklyPaper parallel world ticking mod (make configurable) ++ if (SparklyPaperParallelWorldTicking.enabled) ++ worldIn.turbo.updateSurroundingRedstone(worldIn, pos, state, source); // SparklyPaper - parallel world ticking ++ else ++ turbo.updateSurroundingRedstone(worldIn, pos, state, source); ++ // Leaf end + return; + } + updatePowerStrength(worldIn, pos, state, orientation, blockAdded); +@@ -336,7 +342,12 @@ public class RedStoneWireBlock extends Block { + // [Space Walker] suppress shape updates and emit those manually to + // bypass the new neighbor update stack. + if (level.setBlock(pos, state, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS)) { +- turbo.updateNeighborShapes(level, pos, state); ++ // Leaf - SparklyPaper parallel world ticking mod (make configurable) ++ if (SparklyPaperParallelWorldTicking.enabled) ++ level.turbo.updateNeighborShapes(level, pos, state); // SparklyPaper - parallel world ticking ++ else ++ turbo.updateNeighborShapes(level, pos, state); ++ // Leaf end + } + } + } +diff --git a/net/minecraft/world/level/block/SaplingBlock.java b/net/minecraft/world/level/block/SaplingBlock.java +index e014f052e9b0f5ca6b28044e2389782b7d0e0cb8..7532456b306cff53cd43e631960a093b8e3cc693 100644 +--- a/net/minecraft/world/level/block/SaplingBlock.java ++++ b/net/minecraft/world/level/block/SaplingBlock.java +@@ -16,6 +16,8 @@ import net.minecraft.world.level.block.state.properties.BlockStateProperties; + import net.minecraft.world.level.block.state.properties.IntegerProperty; + import net.minecraft.world.phys.shapes.CollisionContext; + import net.minecraft.world.phys.shapes.VoxelShape; ++import org.bukkit.TreeType; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + + public class SaplingBlock extends BushBlock implements BonemealableBlock { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( +@@ -27,6 +29,27 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { + protected static final VoxelShape SHAPE = Block.box(2.0, 0.0, 2.0, 14.0, 12.0, 14.0); + protected final TreeGrower treeGrower; + public static org.bukkit.TreeType treeType; // CraftBukkit ++ public static final ThreadLocal treeTypeRT = new ThreadLocal<>(); // SparklyPaper - parallel world ticking (from Folia) ++ ++ // Leaf - SparklyPaper parallel world ticking mod ++ // refer to original field in case plugins attempt to modify it ++ public static TreeType getTreeTypeRT() { ++ TreeType treeTypeRTCopy; ++ if (SparklyPaperParallelWorldTicking.enabled && (treeTypeRTCopy = treeTypeRT.get()) != null) ++ return treeTypeRTCopy; ++ synchronized (SaplingBlock.class) { ++ return treeType; ++ } ++ } ++ ++ // update original field in case plugins attempt to access it ++ public static void setTreeTypeRT(TreeType value) { ++ if (SparklyPaperParallelWorldTicking.enabled) ++ treeTypeRT.set(value); ++ synchronized (SaplingBlock.class) { ++ treeType = value; ++ } ++ } + + @Override + public MapCodec codec() { +@@ -63,14 +86,14 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { + this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random); + level.captureTreeGeneration = false; + if (!level.capturedBlockStates.isEmpty()) { +- org.bukkit.TreeType treeType = SaplingBlock.treeType; +- SaplingBlock.treeType = null; ++ org.bukkit.TreeType treeTypeLocal = SaplingBlock.getTreeTypeRT(); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) ++ SaplingBlock.setTreeTypeRT(null); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level.getWorld()); + java.util.List blocks = new java.util.ArrayList<>(level.capturedBlockStates.values()); + level.capturedBlockStates.clear(); + org.bukkit.event.world.StructureGrowEvent event = null; +- if (treeType != null) { +- event = new org.bukkit.event.world.StructureGrowEvent(location, treeType, false, null, blocks); ++ if (treeTypeLocal != null) { ++ event = new org.bukkit.event.world.StructureGrowEvent(location, treeTypeLocal, false, null, blocks); + org.bukkit.Bukkit.getPluginManager().callEvent(event); + } + if (event == null || !event.isCancelled()) { +diff --git a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +index 26db603ed681a6c302596627d4dd5bf8a9bafc4e..97c2620f7d9d0631961224334377739ca683a00c 100644 +--- a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +@@ -21,6 +21,7 @@ import net.minecraft.world.inventory.AbstractContainerMenu; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.component.ItemContainerContents; + import net.minecraft.world.level.block.state.BlockState; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + + public abstract class BaseContainerBlockEntity extends BlockEntity implements Container, MenuProvider, Nameable { + public LockCode lockKey = LockCode.NO_LOCK; +@@ -77,6 +78,12 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co + return canUnlock(player, code, displayName, null); + } + public static boolean canUnlock(Player player, LockCode code, Component displayName, @Nullable BlockEntity blockEntity) { ++ // SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) ++ if (SparklyPaperParallelWorldTicking.enabled && player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != serverPlayer.serverLevel()) { // Leaf - SparklyPaper parallel world ticking mod (make configurable) ++ net.minecraft.server.MinecraftServer.LOGGER.warn("Player " + serverPlayer.getScoreboardName() + " (" + serverPlayer.getStringUUID() + ") attempted to open a BlockEntity @ " + blockEntity.getLevel().getWorld().getName() + " " + blockEntity.getBlockPos().getX() + ", " + blockEntity.getBlockPos().getY() + ", " + blockEntity.getBlockPos().getZ() + " while they were in a different world " + serverPlayer.level().getWorld().getName() + " than the block themselves!"); ++ return false; ++ } ++ // SparklyPaper end + if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != null && blockEntity.getLevel().getBlockEntity(blockEntity.getBlockPos()) == blockEntity) { + final org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(blockEntity.getLevel(), blockEntity.getBlockPos()); + net.kyori.adventure.text.Component lockedMessage = net.kyori.adventure.text.Component.translatable("container.isLocked", io.papermc.paper.adventure.PaperAdventure.asAdventure(displayName)); +diff --git a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java +index 62038854696bd946f58e0e8d26da02415c34e4b1..0f7ac30a53d38fc970d7317b6ed7a83bd9e3f37c 100644 +--- a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java +@@ -43,9 +43,9 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi + // Paper end - Fix NPE in SculkBloomEvent world access + + public static void serverTick(Level level, BlockPos pos, BlockState state, SculkCatalystBlockEntity sculkCatalyst) { +- org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = sculkCatalyst.getBlockPos(); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. ++ org.bukkit.craftbukkit.event.CraftEventFactory.setSourceBlockOverrideRT(pos); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + sculkCatalyst.catalystListener.getSculkSpreader().updateCursors(level, pos, level.getRandom(), true); +- org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = null; // CraftBukkit ++ org.bukkit.craftbukkit.event.CraftEventFactory.setSourceBlockOverrideRT(null); // CraftBukkit // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + } + + @Override +diff --git a/net/minecraft/world/level/block/grower/TreeGrower.java b/net/minecraft/world/level/block/grower/TreeGrower.java +index cf7311c507de09a8f89934e430b2201e8bdffe51..ad90ff29928c4415e17d84fb4e8bae68c215b781 100644 +--- a/net/minecraft/world/level/block/grower/TreeGrower.java ++++ b/net/minecraft/world/level/block/grower/TreeGrower.java +@@ -204,55 +204,59 @@ public final class TreeGrower { + // CraftBukkit start + private void setTreeType(Holder> holder) { + ResourceKey> treeFeature = holder.unwrapKey().get(); ++ // SparklyPaper start - parallel world ticking ++ org.bukkit.TreeType treeType; + if (treeFeature == TreeFeatures.OAK || treeFeature == TreeFeatures.OAK_BEES_005) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TREE; ++ treeType = org.bukkit.TreeType.TREE; + } else if (treeFeature == TreeFeatures.HUGE_RED_MUSHROOM) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.RED_MUSHROOM; ++ treeType = org.bukkit.TreeType.RED_MUSHROOM; + } else if (treeFeature == TreeFeatures.HUGE_BROWN_MUSHROOM) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BROWN_MUSHROOM; ++ treeType = org.bukkit.TreeType.BROWN_MUSHROOM; + } else if (treeFeature == TreeFeatures.JUNGLE_TREE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.COCOA_TREE; ++ treeType = org.bukkit.TreeType.COCOA_TREE; + } else if (treeFeature == TreeFeatures.JUNGLE_TREE_NO_VINE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SMALL_JUNGLE; ++ treeType = org.bukkit.TreeType.SMALL_JUNGLE; + } else if (treeFeature == TreeFeatures.PINE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_REDWOOD; ++ treeType = org.bukkit.TreeType.TALL_REDWOOD; + } else if (treeFeature == TreeFeatures.SPRUCE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.REDWOOD; ++ treeType = org.bukkit.TreeType.REDWOOD; + } else if (treeFeature == TreeFeatures.ACACIA) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.ACACIA; ++ treeType = org.bukkit.TreeType.ACACIA; + } else if (treeFeature == TreeFeatures.BIRCH || treeFeature == TreeFeatures.BIRCH_BEES_005) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIRCH; ++ treeType = org.bukkit.TreeType.BIRCH; + } else if (treeFeature == TreeFeatures.SUPER_BIRCH_BEES_0002) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_BIRCH; ++ treeType = org.bukkit.TreeType.TALL_BIRCH; + } else if (treeFeature == TreeFeatures.SWAMP_OAK) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SWAMP; ++ treeType = org.bukkit.TreeType.SWAMP; + } else if (treeFeature == TreeFeatures.FANCY_OAK || treeFeature == TreeFeatures.FANCY_OAK_BEES_005) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIG_TREE; ++ treeType = org.bukkit.TreeType.BIG_TREE; + } else if (treeFeature == TreeFeatures.JUNGLE_BUSH) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE_BUSH; ++ treeType = org.bukkit.TreeType.JUNGLE_BUSH; + } else if (treeFeature == TreeFeatures.DARK_OAK) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.DARK_OAK; ++ treeType = org.bukkit.TreeType.DARK_OAK; + } else if (treeFeature == TreeFeatures.MEGA_SPRUCE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_REDWOOD; ++ treeType = org.bukkit.TreeType.MEGA_REDWOOD; + } else if (treeFeature == TreeFeatures.MEGA_PINE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_PINE; ++ treeType = org.bukkit.TreeType.MEGA_PINE; + } else if (treeFeature == TreeFeatures.MEGA_JUNGLE_TREE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE; ++ treeType = org.bukkit.TreeType.JUNGLE; + } else if (treeFeature == TreeFeatures.AZALEA_TREE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.AZALEA; ++ treeType = org.bukkit.TreeType.AZALEA; + } else if (treeFeature == TreeFeatures.MANGROVE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MANGROVE; ++ treeType = org.bukkit.TreeType.MANGROVE; + } else if (treeFeature == TreeFeatures.TALL_MANGROVE) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_MANGROVE; ++ treeType = org.bukkit.TreeType.TALL_MANGROVE; + } else if (treeFeature == TreeFeatures.CHERRY || treeFeature == TreeFeatures.CHERRY_BEES_005) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.CHERRY; ++ treeType = org.bukkit.TreeType.CHERRY; + } else if (treeFeature == TreeFeatures.PALE_OAK || treeFeature == TreeFeatures.PALE_OAK_BONEMEAL) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK; ++ treeType = org.bukkit.TreeType.PALE_OAK; + } else if (treeFeature == TreeFeatures.PALE_OAK_CREAKING) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK_CREAKING; ++ treeType = org.bukkit.TreeType.PALE_OAK_CREAKING; + } else { + throw new IllegalArgumentException("Unknown tree generator " + treeFeature); + } ++ net.minecraft.world.level.block.SaplingBlock.setTreeTypeRT(treeType); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) ++ // SparklyPaper end + } + // CraftBukkit end + } +diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java +index 238e015d4ff5fabb99e569118f253366d545d269..23446968f9b42f904da189fb6bc3a6fe15c01fe7 100644 +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java +@@ -48,6 +48,7 @@ import net.minecraft.world.level.material.FluidState; + import net.minecraft.world.level.material.Fluids; + import net.minecraft.world.ticks.LevelChunkTicks; + import net.minecraft.world.ticks.TickContainerAccess; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + import org.slf4j.Logger; + + public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk, ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk, ca.spottedleaf.moonrise.patches.getblock.GetBlockChunk { // Paper - rewrite chunk system // Paper - get block chunk optimisation +@@ -401,6 +402,8 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + + @Nullable + public BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving, boolean doPlace) { ++ if (SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper parallel world ticking mod (make configurable) ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, pos, "Updating block asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) + // CraftBukkit end + int y = pos.getY(); + LevelChunkSection section = this.getSection(this.getSectionIndex(y)); +diff --git a/net/minecraft/world/level/entity/EntityTickList.java b/net/minecraft/world/level/entity/EntityTickList.java +index dec51066fc3f57b7bdc56195313c219f45a7fbee..0d45962fd2a3f05319fe6aca93e9a1ee58152259 100644 +--- a/net/minecraft/world/level/entity/EntityTickList.java ++++ b/net/minecraft/world/level/entity/EntityTickList.java +@@ -7,20 +7,40 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; + import java.util.function.Consumer; + import javax.annotation.Nullable; + import net.minecraft.world.entity.Entity; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + + public class EntityTickList { + public final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system // Pufferfish - private->public + ++ // Leaf - SparklyPaper parallel world ticking mod ++ // preserve original constructor ++ public EntityTickList() { ++ this(null); ++ } ++ ++ // SparklyPaper start - parallel world ticking ++ // Used to track async entity additions/removals/loops ++ @Nullable // Leaf - SparklyPaper parallel world ticking mod (preserve original constructor) ++ private final net.minecraft.server.level.ServerLevel serverLevel; ++ public EntityTickList(@Nullable net.minecraft.server.level.ServerLevel serverLevel) { // Leaf - SparklyPaper parallel world ticking mod (preserve original constructor) ++ this.serverLevel = serverLevel; ++ } ++ // SparklyPaper end ++ + private void ensureActiveIsNotIterated() { + // Paper - rewrite chunk system + } + + public void add(Entity entity) { ++ if (SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper parallel world ticking mod (make configurable) ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist addition"); // Paper // SparklyPaper - parallel world ticking (additional concurrency issues logs) + this.ensureActiveIsNotIterated(); + this.entities.add(entity); // Paper - rewrite chunk system + } + + public void remove(Entity entity) { ++ if (SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper parallel world ticking mod (make configurable) ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist removal"); // Paper // SparklyPaper - parallel world ticking (additional concurrency issues logs) + this.ensureActiveIsNotIterated(); + this.entities.remove(entity); // Paper - rewrite chunk system + } +@@ -30,6 +50,8 @@ public class EntityTickList { + } + + public void forEach(Consumer entity) { ++ if (SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper parallel world ticking mod (make configurable) ++ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverLevel, "Asynchronous entity ticklist iteration"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) + // Paper start - rewrite chunk system + // To ensure nothing weird happens with dimension travelling, do not iterate over new entries... + // (by dfl iterator() is configured to not iterate over new entries) +diff --git a/net/minecraft/world/level/saveddata/maps/MapIndex.java b/net/minecraft/world/level/saveddata/maps/MapIndex.java +index ffe604f8397a002800e6ecc2f878d0f6f1c98703..ae5dcff21b597182af457c07c8a995d77ae32f5d 100644 +--- a/net/minecraft/world/level/saveddata/maps/MapIndex.java ++++ b/net/minecraft/world/level/saveddata/maps/MapIndex.java +@@ -34,17 +34,21 @@ public class MapIndex extends SavedData { + + @Override + public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) { +- for (Entry entry : this.usedAuxIds.object2IntEntrySet()) { +- tag.putInt(entry.getKey(), entry.getIntValue()); +- } ++ synchronized (this.usedAuxIds) { // SparklyPaper start - make map data thread-safe ++ for (Entry entry : this.usedAuxIds.object2IntEntrySet()) { ++ tag.putInt(entry.getKey(), entry.getIntValue()); ++ } ++ } // SparklyPaper end - make map data thread-safe + + return tag; + } + + public MapId getFreeAuxValueForMap() { +- int i = this.usedAuxIds.getInt("map") + 1; +- this.usedAuxIds.put("map", i); +- this.setDirty(); +- return new MapId(i); ++ synchronized (this.usedAuxIds) { // SparklyPaper start - make map data thread-safe ++ int i = this.usedAuxIds.getInt("map") + 1; ++ this.usedAuxIds.put("map", i); ++ this.setDirty(); ++ return new MapId(i); ++ } // SparklyPaper end - make map data thread-safe + } + } diff --git a/leaf-server/minecraft-patches/features/0142-Track-each-world-MSPT.patch b/leaf-server/minecraft-patches/features/0142-Track-each-world-MSPT.patch new file mode 100644 index 00000000..378399ac --- /dev/null +++ b/leaf-server/minecraft-patches/features/0142-Track-each-world-MSPT.patch @@ -0,0 +1,137 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Thu, 6 Mar 2025 00:26:19 +0100 +Subject: [PATCH] Track each world MSPT + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index dd1c55e983bc1ddc9a77a0b825b78eba62c201ec..f7a3cdb3953a959fb258fcb0eeea81ea2c8b217a 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1687,7 +1687,16 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0L) { ++ total += value; ++ count++; ++ } ++ } ++ ++ if (count == 0) return 0.0; ++ return (double) total / (double) count * 1.0E-6D; ++ } ++ + public enum FillMode { + TPS, MSPT, PING + } diff --git a/leaf-server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch b/leaf-server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch new file mode 100644 index 00000000..794706ee --- /dev/null +++ b/leaf-server/paper-patches/features/0031-SparklyPaper-Parallel-world-ticking.patch @@ -0,0 +1,695 @@ +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 + + +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..8d2694977f29b680d9daf83f2c8cc3502d3cb7dd 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java ++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java +@@ -6,6 +6,7 @@ import net.minecraft.world.level.ChunkPos; + import net.minecraft.world.level.Level; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + +@@ -14,6 +15,7 @@ import java.util.concurrent.atomic.AtomicInteger; + public class TickThread extends Thread { + + private static final Logger LOGGER = LoggerFactory.getLogger(TickThread.class); ++ public static final boolean HARD_THROW = !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 +28,15 @@ 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()); + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } +@@ -42,7 +45,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()); + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } +@@ -51,7 +54,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()); + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } +@@ -60,7 +63,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()); + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } +@@ -69,7 +72,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()); + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } +@@ -78,7 +81,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()); + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } +@@ -87,12 +90,74 @@ 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()); + LOGGER.error(ex, new Throwable()); + throw new IllegalStateException(ex); + } + } + ++ // SparklyPaper - parallel world ticking ++ // This is an additional method to check if the tick thread is bound to a specific world because, by default, Paper's isTickThread methods do not provide this information ++ // Because we only tick worlds in parallel (instead of regions), we can use this for our checks ++ public static 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()); ++ if (HARD_THROW) ++ throw new IllegalStateException(reason); ++ } ++ } ++ ++ // SparklyPaper - parallel world ticking ++ // 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); ++ } ++ } ++ ++ // SparklyPaper - parallel world ticking ++ // 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; ++ } ++ + 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 +198,73 @@ 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 - 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) ++ } ++ ++ // SparklyPaper start - parallel world ticking ++ 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 + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +index 15673166e566b2a6d5093210d99b154e69fab0ad..3c80b73f969318ec12b4f39b50cf1e215b5ca23f 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +@@ -145,6 +145,7 @@ import org.bukkit.util.NumberConversions; + import org.bukkit.util.RayTraceResult; + import org.bukkit.util.StructureSearchResult; + import org.bukkit.util.Vector; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + +@@ -455,7 +456,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 (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 + if (!this.isChunkLoaded(x, z)) { + return true; + } +@@ -472,6 +478,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean refreshChunk(int x, int z) { ++ if (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 +530,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 (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 + 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 +763,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { ++ if (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 +880,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 (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 +973,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { ++ if (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 +1005,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 (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 +2349,8 @@ public class CraftWorld extends CraftRegionAccessor implements World { + + @Override + public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { ++ if (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 973b297a22c0cc53f966582c67c3688f4b2205c7..747f20d080b602ab9a1c551c648bf54682e584e0 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +@@ -60,6 +60,7 @@ import org.bukkit.util.BlockVector; + import org.bukkit.util.BoundingBox; + import org.bukkit.util.RayTraceResult; + import org.bukkit.util.Vector; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + + public class CraftBlock implements Block { + private final net.minecraft.world.level.LevelAccessor world; +@@ -75,6 +76,11 @@ public class CraftBlock implements Block { + } + + public net.minecraft.world.level.block.state.BlockState getNMS() { ++ // Folia start - parallel world ticking ++ if (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"); ++ } ++ // Folia end - parallel world ticking + return this.world.getBlockState(this.position); + } + +@@ -157,6 +163,11 @@ public class CraftBlock implements Block { + } + + private void setData(final byte data, int flag) { ++ // SparklyPaper start - parallel world ticking ++ if (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 +209,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 (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 +360,33 @@ public class CraftBlock implements Block { + + @Override + public Biome getBiome() { ++ // SparklyPaper start - parallel world ticking ++ if (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 (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 (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 +407,11 @@ public class CraftBlock implements Block { + + @Override + public boolean isBlockIndirectlyPowered() { ++ // SparklyPaper start - parallel world ticking ++ if (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 +451,11 @@ public class CraftBlock implements Block { + + @Override + public int getBlockPower(BlockFace face) { ++ // SparklyPaper start - parallel world ticking ++ if (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 +526,11 @@ public class CraftBlock implements Block { + + @Override + public boolean breakNaturally() { ++ // SparklyPaper start - parallel world ticking ++ if (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 +590,11 @@ public class CraftBlock implements Block { + + @Override + public boolean applyBoneMeal(BlockFace face) { ++ // SparklyPaper start - parallel world ticking ++ if (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 +606,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 +696,11 @@ public class CraftBlock implements Block { + + @Override + public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { ++ // SparklyPaper start - parallel world ticking ++ if (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 +742,11 @@ public class CraftBlock implements Block { + + @Override + public boolean canPlace(BlockData data) { ++ // SparklyPaper start - parallel world ticking ++ if (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 +781,11 @@ public class CraftBlock implements Block { + + @Override + public void tick() { ++ // SparklyPaper start - parallel world ticking ++ if (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..bad74104dcfde34ae0dec99adfe81e71132b7c28 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +@@ -11,12 +11,14 @@ import net.minecraft.network.protocol.game.ClientGamePacketListener; + import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; + import net.minecraft.server.MinecraftServer; + import net.minecraft.world.level.LevelAccessor; ++import net.minecraft.world.level.block.DispenserBlock; + import net.minecraft.world.level.block.entity.BlockEntity; + import org.bukkit.Location; + import org.bukkit.World; + import org.bukkit.block.TileState; + import org.bukkit.craftbukkit.util.CraftLocation; + import org.bukkit.persistence.PersistentDataContainer; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + +@@ -26,6 +28,26 @@ 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 - SparklyPaper parallel world ticking mod ++ // refer to original field in case plugins attempt to modify it ++ public static boolean getDisableSnapshotTL() { ++ if (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 (SparklyPaperParallelWorldTicking.enabled) ++ DISABLE_SNAPSHOT_TL.set(value); ++ synchronized (CraftBlockEntityState.class) { ++ DISABLE_SNAPSHOT = value; ++ } ++ } + + public CraftBlockEntityState(World world, T tileEntity) { + super(world, tileEntity.getBlockPos(), tileEntity.getBlockState()); +@@ -34,8 +56,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 (getDisableSnapshotTL()) { // 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..bc0f999f89fedebe58b6fa554c2abbacb65adbeb 100644 +--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java ++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +@@ -21,6 +21,7 @@ import org.bukkit.material.Attachable; + import org.bukkit.material.MaterialData; + import org.bukkit.metadata.MetadataValue; + import org.bukkit.plugin.Plugin; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + + public class CraftBlockState implements BlockState { + +@@ -215,6 +216,12 @@ public class CraftBlockState implements BlockState { + LevelAccessor access = this.getWorldHandle(); + CraftBlock block = this.getBlock(); + ++ // SparklyPaper start - parallel world ticking ++ if (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 +357,8 @@ public class CraftBlockState implements BlockState { + + @Override + public java.util.Collection getDrops(org.bukkit.inventory.ItemStack item, org.bukkit.entity.Entity entity) { ++ if (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..11a3970a9f3bee1d05327b8a5c5dde4f746929ac 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 fbb4dd93c263c898731902b73dbd1c62df1eea4b..8b78410875c8b1c7367271e6bf4c085f04a19ab5 100644 +--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java ++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +@@ -254,6 +254,7 @@ import org.bukkit.inventory.meta.BookMeta; + import org.bukkit.inventory.view.AnvilView; + import org.bukkit.potion.PotionEffect; + import org.bukkit.util.Vector; ++import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; + + public class CraftEventFactory { + +@@ -961,6 +962,27 @@ 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 - SparklyPaper parallel world ticking mod ++ // refer to original field in case plugins attempt to modify it ++ public static BlockPos getSourceBlockOverrideRT() { ++ BlockPos sourceBlockOverrideRTCopy; ++ if (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 (SparklyPaperParallelWorldTicking.enabled) ++ sourceBlockOverrideRT.set(value); ++ synchronized (CraftEventFactory.class) { ++ sourceBlockOverride = value; ++ } ++ } + + 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,7 @@ 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); ++ BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, CraftEventFactory.getSourceBlockOverrideRT() != null ? CraftEventFactory.sourceBlockOverrideRT.get() : source), state); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper parallel world ticking mod (collapse original behavior) + Bukkit.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { +@@ -2262,7 +2284,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/leaf-server/src/main/java/org/dreeam/leaf/async/world/SparklyPaperServerLevelTickExecutorThreadFactory.java b/leaf-server/src/main/java/org/dreeam/leaf/async/world/SparklyPaperServerLevelTickExecutorThreadFactory.java new file mode 100644 index 00000000..2a359fee --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/async/world/SparklyPaperServerLevelTickExecutorThreadFactory.java @@ -0,0 +1,29 @@ +package org.dreeam.leaf.async.world; + +import ca.spottedleaf.moonrise.common.util.TickThread; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.ThreadFactory; + +public class SparklyPaperServerLevelTickExecutorThreadFactory implements ThreadFactory { + private final String worldName; + + public SparklyPaperServerLevelTickExecutorThreadFactory(final String worldName) { + this.worldName = worldName; + } + + @Override + public Thread newThread(@NotNull Runnable runnable) { + TickThread.ServerLevelTickThread tickThread = new TickThread.ServerLevelTickThread(runnable, "LeafParallelWorld-ticker-worker " + this.worldName); + + if (tickThread.isDaemon()) { + tickThread.setDaemon(false); + } + + if (tickThread.getPriority() != 5) { + tickThread.setPriority(5); + } + + return tickThread; + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/command/LeafCommand.java b/leaf-server/src/main/java/org/dreeam/leaf/command/LeafCommand.java index 251020ca..09ec7aeb 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/command/LeafCommand.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/command/LeafCommand.java @@ -5,6 +5,7 @@ import it.unimi.dsi.fastutil.Pair; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.minecraft.Util; +import org.dreeam.leaf.command.subcommands.MSPTCommand; import org.dreeam.leaf.command.subcommands.ReloadCommand; import org.dreeam.leaf.command.subcommands.VersionCommand; import org.jetbrains.annotations.NotNull; @@ -37,11 +38,13 @@ public final class LeafCommand extends Command { // subcommand label -> subcommand private static final LeafSubcommand RELOAD_SUBCOMMAND = new ReloadCommand(); private static final LeafSubcommand VERSION_SUBCOMMAND = new VersionCommand(); + private static final LeafSubcommand MSPT_SUBCOMMAND = new MSPTCommand(); private static final Map SUBCOMMANDS = Util.make(() -> { final Map, LeafSubcommand> commands = new HashMap<>(); commands.put(Set.of(ReloadCommand.LITERAL_ARGUMENT), RELOAD_SUBCOMMAND); commands.put(Set.of(VersionCommand.LITERAL_ARGUMENT), VERSION_SUBCOMMAND); + commands.put(Set.of(MSPTCommand.LITERAL_ARGUMENT), MSPT_SUBCOMMAND); return commands.entrySet().stream() .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) diff --git a/leaf-server/src/main/java/org/dreeam/leaf/command/subcommands/MSPTCommand.java b/leaf-server/src/main/java/org/dreeam/leaf/command/subcommands/MSPTCommand.java new file mode 100644 index 00000000..9e8c0015 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/command/subcommands/MSPTCommand.java @@ -0,0 +1,244 @@ +package org.dreeam.leaf.command.subcommands; + +import net.kyori.adventure.text.Component; +import net.minecraft.server.MinecraftServer; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.dreeam.leaf.command.LeafCommand; +import org.dreeam.leaf.command.PermissionedLeafSubcommand; +import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking; +import org.bukkit.command.CommandSender; +import org.bukkit.permissions.PermissionDefault; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.*; + +@DefaultQualifier(NonNull.class) +public final class MSPTCommand extends PermissionedLeafSubcommand { + + public static final String LITERAL_ARGUMENT = "mspt"; + public static final String PERM = LeafCommand.BASE_PERM + "." + LITERAL_ARGUMENT; + private static final DecimalFormat DF = new DecimalFormat("########0.0"); + private static final Component SLASH = text("/"); + + public MSPTCommand() { + super(PERM, PermissionDefault.TRUE); + } + + @Override + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { + // Check if parallel world ticking is enabled + if (!SparklyPaperParallelWorldTicking.enabled) { + sender.sendMessage(Component.text() + .content("Per-world MSPT tracking is only available when parallel world ticking is enabled.") + .color(RED) + .build()); + sender.sendMessage(Component.text() + .content("Please enable it in your Leaf configuration to use this command.") + .color(GRAY) + .build()); + return true; + } + + // Check if compact mode is requested + boolean compactMode = args.length > 0 && args[0].equalsIgnoreCase("compact"); + + MinecraftServer server = MinecraftServer.getServer(); + + if (compactMode) { + displayCompactStats(sender, server); + } else { + // Display header + sender.sendMessage(Component.text() + .content("━━━━━━━━━━━━━ ") + .color(GOLD) + .append(Component.text("MSPT Statistics").color(YELLOW)) + .append(Component.text(" ━━━━━━━━━━━━━").color(GOLD)) + .build()); + + // Overall server MSPT + displayServerMSPT(sender, server); + + // Add separator + sender.sendMessage(Component.text("")); + + // World-specific MSPT + displayWorldMSPT(sender, server); + } + + return true; + } + + private void displayCompactStats(CommandSender sender, MinecraftServer server) { + // Get server stats (only 5s data with avg/min/max) + List serverTimes = eval(server.tickTimes5s.getTimes()); + + // Display server stats in compact form + sender.sendMessage(Component.text() + .content("Server: ") + .color(GOLD) + .append(serverTimes.get(0)).append(SLASH).append(serverTimes.get(1)).append(SLASH).append(serverTimes.get(2)) + .build()); + + // Display world stats in compact form + for (net.minecraft.server.level.ServerLevel serverLevel : server.getAllLevels()) { + List worldTimes = eval(serverLevel.tickTimes5s.getTimes()); + + sender.sendMessage(Component.text() + .content(serverLevel.getWorld().getName() + ": ") + .color(GOLD) + .append(worldTimes.get(0)).append(SLASH).append(worldTimes.get(1)).append(SLASH).append(worldTimes.get(2)) + .build()); + } + } + + private void displayServerMSPT(CommandSender sender, MinecraftServer server) { + List times = new ArrayList<>(); + times.addAll(eval(server.tickTimes5s.getTimes())); + times.addAll(eval(server.tickTimes10s.getTimes())); + times.addAll(eval(server.tickTimes60s.getTimes())); + + sender.sendMessage(Component.text() + .content("Server tick times ") + .color(GOLD) + .append(Component.text() + .content("(avg/min/max)") + .color(YELLOW) + ) + .build()); + + sender.sendMessage(Component.text() + .content(" 5s: ") + .color(GOLD) + .append(times.get(0)).append(SLASH).append(times.get(1)).append(SLASH).append(times.get(2)) + .build()); + + sender.sendMessage(Component.text() + .content(" 10s: ") + .color(GOLD) + .append(times.get(3)).append(SLASH).append(times.get(4)).append(SLASH).append(times.get(5)) + .build()); + + sender.sendMessage(Component.text() + .content(" 60s: ") + .color(GOLD) + .append(times.get(6)).append(SLASH).append(times.get(7)).append(SLASH).append(times.get(8)) + .build()); + } + + private void displayWorldMSPT(CommandSender sender, MinecraftServer server) { + sender.sendMessage(Component.text() + .content("World-specific tick times ") + .color(GOLD) + .append(Component.text() + .content("(avg/min/max)") + .color(YELLOW) + ) + .build()); + + for (net.minecraft.server.level.ServerLevel serverLevel : server.getAllLevels()) { + List worldTimes = new ArrayList<>(); + worldTimes.addAll(eval(serverLevel.tickTimes5s.getTimes())); + worldTimes.addAll(eval(serverLevel.tickTimes10s.getTimes())); + worldTimes.addAll(eval(serverLevel.tickTimes60s.getTimes())); + + // World name header + sender.sendMessage(Component.text() + .content("➤ ") + .color(YELLOW) + .append(Component.text(serverLevel.getWorld().getName()).color(GOLD)) + .build()); + + // Display time periods + sender.sendMessage(Component.text() + .content(" 5s: ") + .color(GRAY) + .append(worldTimes.get(0)).append(SLASH).append(worldTimes.get(1)).append(SLASH).append(worldTimes.get(2)) + .build()); + + sender.sendMessage(Component.text() + .content(" 10s: ") + .color(GRAY) + .append(worldTimes.get(3)).append(SLASH).append(worldTimes.get(4)).append(SLASH).append(worldTimes.get(5)) + .build()); + + sender.sendMessage(Component.text() + .content(" 60s: ") + .color(GRAY) + .append(worldTimes.get(6)).append(SLASH).append(worldTimes.get(7)).append(SLASH).append(worldTimes.get(8)) + .build()); + + boolean hasMoreWorlds = false; + Iterable levels = server.getAllLevels(); + for (net.minecraft.server.level.ServerLevel level : levels) { + if (level != serverLevel) { + hasMoreWorlds = true; + break; + } + } + + if (hasMoreWorlds) { + sender.sendMessage(Component.text("")); + } + } + } + + private static List eval(long[] times) { + long min = Integer.MAX_VALUE; + long max = 0L; + long total = 0L; + int count = 0; + + for (long value : times) { + if (value > 0L) { + count++; + if (value < min) min = value; + if (value > max) max = value; + total += value; + } + } + + if (count == 0) { + // No data available yet + return Arrays.asList( + text("N/A", GRAY), + text("N/A", GRAY), + text("N/A", GRAY) + ); + } + + double avgD = ((double) total / (double) count) * 1.0E-6D; + double minD = ((double) min) * 1.0E-6D; + double maxD = ((double) max) * 1.0E-6D; + + return Arrays.asList(getColoredValue(avgD), getColoredValue(minD), getColoredValue(maxD)); + } + + private static Component getColoredValue(double value) { + return text(DF.format(value) + "ms", + value >= 50 ? RED : + value >= 40 ? YELLOW : + value >= 30 ? GOLD : + value >= 20 ? GREEN : + AQUA); + } + + @Override + public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { + if (!SparklyPaperParallelWorldTicking.enabled) { + return Collections.emptyList(); + } + + if (args.length == 1) { + return Collections.singletonList("compact"); + } + + return Collections.emptyList(); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncChunkSend.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncChunkSend.java index a05387cb..ec41f9a3 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncChunkSend.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/AsyncChunkSend.java @@ -17,11 +17,9 @@ public class AsyncChunkSend extends ConfigModules { public void onLoaded() { config.addCommentRegionBased(getBasePath(), """ - **Experimental feature** Makes chunk packet preparation and sending asynchronous to improve server performance. This can significantly reduce main thread load when many players are loading chunks.""", """ - **实验性功能** 使区块数据包准备和发送异步化以提高服务器性能. 当许多玩家同时加载区块时, 这可以显著减少主线程负载."""); 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 new file mode 100644 index 00000000..c93d5f46 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/async/SparklyPaperParallelWorldTicking.java @@ -0,0 +1,34 @@ +package org.dreeam.leaf.config.modules.async; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class SparklyPaperParallelWorldTicking extends ConfigModules { + public String getBasePath() { + return EnumConfigCategory.ASYNC.getBaseKeyName() + ".parallel-world-tracking"; + } + + public static boolean enabled = false; + public static int threads = 8; + public static boolean logContainerCreationStacktraces = false; + public static boolean disableHardThrow = false; + + @Override + public void onLoaded() { + config.addCommentRegionBased(getBasePath(), + """ + **Experimental feature** + Enables parallel world ticking to improve performance on multi-core systems..""", + """ + **实验性功能** + 启用并行世界处理以提高多核系统的性能."""); + + enabled = config.getBoolean(getBasePath() + ".enabled", enabled); + threads = config.getInt(getBasePath() + ".threads", threads); + threads = enabled ? 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; + } +}