From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: MrPowerGamerBR Date: Tue, 7 Nov 2023 01:34:14 -0300 Subject: [PATCH] Parallel world ticking "mom can we have folia?" "we already have folia at home" folia at home: 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 217d1f908a36a5177ba3cbb80a33f73d4dab0fa0..aec59213525039d6cf38ab8c3acfd45116c75eed 100644 --- a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java +++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java @@ -14,6 +14,7 @@ import java.util.concurrent.atomic.AtomicInteger; public class TickThread extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(TickThread.class); + public static final boolean HARD_THROW = !Boolean.getBoolean("sparklypaper.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 /** * @deprecated @@ -22,52 +23,121 @@ public class TickThread extends Thread { public static void ensureTickThread(final String reason) { if (!isTickThread()) { LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); - throw new IllegalStateException(reason); + if (HARD_THROW) + throw new IllegalStateException(reason); } } public static void ensureTickThread(final Level world, final BlockPos pos, final String reason) { if (!isTickThreadFor(world, pos)) { - LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); - throw new IllegalStateException(reason); + LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " blockPos: " + pos + " - " + getTickThreadInformation(world.getServer()), new Throwable()); + if (HARD_THROW) + throw new IllegalStateException(reason); } } public static void ensureTickThread(final Level world, final ChunkPos pos, final String reason) { if (!isTickThreadFor(world, pos)) { - LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); - throw new IllegalStateException(reason); + LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " chunkPos: " + pos + " - " + getTickThreadInformation(world.getServer()), new Throwable()); + if (HARD_THROW) + throw new IllegalStateException(reason); } } public static void ensureTickThread(final Level world, final int chunkX, final int chunkZ, final String reason) { if (!isTickThreadFor(world, chunkX, chunkZ)) { - LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); - throw new IllegalStateException(reason); + LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " chunkX: " + chunkX + " chunkZ: " + chunkZ + " - " + getTickThreadInformation(world.getServer()), new Throwable()); + if (HARD_THROW) + throw new IllegalStateException(reason); } } public static void ensureTickThread(final Entity entity, final String reason) { if (!isTickThreadFor(entity)) { - LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); - throw new IllegalStateException(reason); + LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ entity " + entity.getStringUUID() + " - " + getTickThreadInformation(entity.getServer()), new Throwable()); + if (HARD_THROW) + throw new IllegalStateException(reason); } } public static void ensureTickThread(final Level world, final AABB aabb, final String reason) { if (!isTickThreadFor(world, aabb)) { - LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); - throw new IllegalStateException(reason); + LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " aabb: " + aabb + " - " + getTickThreadInformation(world.getServer()), new Throwable()); + if (HARD_THROW) + throw new IllegalStateException(reason); } } public static void ensureTickThread(final Level world, final double blockX, final double blockZ, final String reason) { if (!isTickThreadFor(world, blockX, blockZ)) { - LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); - throw new IllegalStateException(reason); + LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " blockX: " + blockX + " blockZ: " + blockZ + " - " + getTickThreadInformation(world.getServer()), 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 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(); @@ -102,42 +172,111 @@ public class TickThread extends Thread { } public static boolean isTickThreadFor(final Level world, final BlockPos pos) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Level world, final ChunkPos pos) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Level world, final Vec3 pos) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Level world, final AABB aabb) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Level world, final double blockX, final double blockZ) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Level world, final Vec3 position, final Vec3 deltaMovement, final int buffer) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Level world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ, final int radius) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; + } + + // 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) { + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Entity entity) { - return isTickThread(); + if (entity == null) { + return true; + } + + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == entity.level(); + } else return currentThread instanceof TickThread; + } + + // 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/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java index 91a6f57f35fc1553159cca138a0619e703b2b014..dd49dd779f4aa45b27614b581b347b608b1ac64f 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java @@ -1030,7 +1030,7 @@ public final class ChunkHolderManager { if (changedFullStatus.isEmpty()) { return; } - if (!TickThread.isTickThread()) { + if (!TickThread.isTickThreadFor(world)) { // SparklyPaper - parallel world ticking this.taskScheduler.scheduleChunkTask(() -> { final ArrayDeque pendingFullLoadUpdate = ChunkHolderManager.this.pendingFullLoadUpdate; for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { @@ -1056,7 +1056,7 @@ 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"); + TickThread.ensureTickThread(world, "Cannot unload chunks off-main"); // SparklyPaper - parallel world ticking if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { throw new IllegalStateException("Cannot unload chunks recursively"); @@ -1338,7 +1338,7 @@ public final class ChunkHolderManager { List changedFullStatus = null; - final boolean isTickThread = TickThread.isTickThread(); + final boolean isTickThread = TickThread.isTickThreadFor(world); boolean ret = false; final boolean canProcessFullUpdates = processFullUpdates & isTickThread; diff --git a/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java index ddb264443f2e38b6348226016f9139727c588898..5a5451dda717656e02f11c71646f7afeeb0c2232 100644 --- a/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java @@ -54,7 +54,7 @@ public class BoatDispenseItemBehavior extends DefaultDispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d1, d2 + d4, d3)); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking worldserver.getCraftServer().getPluginManager().callEvent(event); } diff --git a/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java index 8aae1d113e84dfad9f2b6f0bcd203ca6c68bc5ce..7fca48f233e98b63a5d8c9094561380866f3d0a2 100644 --- a/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java @@ -87,7 +87,7 @@ public class DefaultDispenseItemBehavior implements DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack); BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), CraftVector.toBukkit(entityitem.getDeltaMovement())); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking world.getCraftServer().getPluginManager().callEvent(event); } diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java index 0d12605dc84dad49faa18bf1fd058c3c168623ee..1a240c6ac7980df94fc40ce8fc6c3c6096db8eef 100644 --- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java @@ -111,7 +111,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking worldserver.getCraftServer().getPluginManager().callEvent(event); } @@ -170,7 +170,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking worldserver.getCraftServer().getPluginManager().callEvent(event); } @@ -225,7 +225,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) list.get(0).getBukkitEntity()); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking world.getCraftServer().getPluginManager().callEvent(event); } @@ -280,7 +280,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) entityhorsechestedabstract.getBukkitEntity()); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking world.getCraftServer().getPluginManager().callEvent(event); } @@ -354,7 +354,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(x, y, z)); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking worldserver.getCraftServer().getPluginManager().callEvent(event); } @@ -419,7 +419,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ())); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking worldserver.getCraftServer().getPluginManager().callEvent(event); } @@ -457,7 +457,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); // Paper - ignore stack size on damageable items BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking worldserver.getCraftServer().getPluginManager().callEvent(event); } @@ -519,7 +519,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking worldserver.getCraftServer().getPluginManager().callEvent(event); } @@ -548,8 +548,8 @@ public interface DispenseItemBehavior { // CraftBukkit start worldserver.captureTreeGeneration = false; if (worldserver.capturedBlockStates.size() > 0) { - TreeType treeType = SaplingBlock.treeType; - SaplingBlock.treeType = null; + TreeType treeType = SaplingBlock.treeTypeRT.get(); // SparklyPaper - parallel world ticking + SaplingBlock.treeTypeRT.set(null); // SparklyPaper - parallel world ticking Location location = CraftLocation.toBukkit(blockposition, worldserver.getWorld()); List blocks = new java.util.ArrayList<>(worldserver.capturedBlockStates.values()); worldserver.capturedBlockStates.clear(); @@ -588,7 +588,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D)); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking worldserver.getCraftServer().getPluginManager().callEvent(event); } @@ -631,7 +631,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ())); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking worldserver.getCraftServer().getPluginManager().callEvent(event); } @@ -680,7 +680,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ())); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking worldserver.getCraftServer().getPluginManager().callEvent(event); } @@ -742,7 +742,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - only single item in event BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ())); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking worldserver.getCraftServer().getPluginManager().callEvent(event); } @@ -824,7 +824,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); // Paper - ignore stack size on damageable items BlockDispenseEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) list.get(0).getBukkitEntity()); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { world.getCraftServer().getPluginManager().callEvent(event); } diff --git a/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java index bf8c511739265c6a9cd277752e844481598f8966..d087b35b212e8bda1f6d8be35ed73fc759718c5e 100644 --- a/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java @@ -50,7 +50,7 @@ public class EquipmentDispenseItemBehavior extends DefaultDispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) entityliving.getBukkitEntity()); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { world.getCraftServer().getPluginManager().callEvent(event); } diff --git a/src/main/java/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java index 3588896b7413be73ade6b3f8fd111d02c48ec550..913ef6c348af35053c93a37bc0ec719563740d56 100644 --- a/src/main/java/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java @@ -74,7 +74,7 @@ public class MinecartDispenseItemBehavior extends DefaultDispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseEvent event = new BlockDispenseEvent(block2, craftItem.clone(), new org.bukkit.util.Vector(vec3d1.x, vec3d1.y, vec3d1.z)); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { worldserver.getCraftServer().getPluginManager().callEvent(event); } diff --git a/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java b/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java index 54c72cf472e06e214eb61bd8615a0bb27690c807..e4bce6bd878be3405691062db2f05492f67cfafb 100644 --- a/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java @@ -43,7 +43,7 @@ public class ProjectileDispenseBehavior extends DefaultDispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) enumdirection.getStepX(), (double) enumdirection.getStepY(), (double) enumdirection.getStepZ())); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { worldserver.getCraftServer().getPluginManager().callEvent(event); } diff --git a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java index 65ed3d77a51b8299517e0c165403b0c5ac413475..f999c33552c611df733da93dcb9699b15fa54a92 100644 --- a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java @@ -41,7 +41,7 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); // Paper - ignore stack size on damageable items BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking worldserver.getCraftServer().getPluginManager().callEvent(event); } diff --git a/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java index 8f9fde5489c0e1d0a91203536caddec5a9c96f6c..84617a7d6fae4a67f07d50a8e9410e359725b675 100644 --- a/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java @@ -37,7 +37,7 @@ public class ShulkerBoxDispenseBehavior extends OptionalDispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ())); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking pointer.level().getCraftServer().getPluginManager().callEvent(event); } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index fd5a0b0eaece1fcee3513df52f0c1ad420832af8..c9149c3b3da4589844ef721a62210b8b47227654 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -3,9 +3,6 @@ package net.minecraft.server; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; -import co.aikar.timings.Timings; -import com.destroystokyo.paper.event.server.PaperServerListPingEvent; -import com.google.common.base.Stopwatch; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -16,66 +13,22 @@ import com.mojang.datafixers.DataFixer; import com.mojang.jtracy.DiscontinuousFrame; import com.mojang.jtracy.TracyClient; import com.mojang.logging.LogUtils; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.Lifecycle; import it.unimi.dsi.fastutil.longs.LongIterator; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectArraySet; -import java.awt.image.BufferedImage; -import java.io.BufferedWriter; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadInfo; -import java.lang.management.ThreadMXBean; -import java.net.Proxy; -import java.nio.file.FileStore; -import java.nio.file.Files; -import java.nio.file.LinkOption; -import java.nio.file.Path; -import java.security.KeyPair; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.LockSupport; -import java.util.function.BooleanSupplier; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import javax.annotation.Nullable; -import javax.imageio.ImageIO; -import net.minecraft.CrashReport; -import net.minecraft.CrashReportCategory; -import net.minecraft.FileUtil; -import net.minecraft.ReportType; -import net.minecraft.ReportedException; -import net.minecraft.SharedConstants; -import net.minecraft.SystemReport; -import net.minecraft.Util; +import joptsimple.OptionSet; +import net.minecraft.*; import net.minecraft.commands.CommandSource; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; -import net.minecraft.core.BlockPos; -import net.minecraft.core.HolderGetter; -import net.minecraft.core.LayeredRegistryAccess; -import net.minecraft.core.Registry; -import net.minecraft.core.RegistryAccess; +import net.minecraft.core.*; import net.minecraft.core.registries.Registries; import net.minecraft.data.worldgen.features.MiscOverworldFeatures; import net.minecraft.gametest.framework.GameTestTicker; +import net.minecraft.nbt.NbtException; +import net.minecraft.nbt.ReportedNbtException; import net.minecraft.network.chat.ChatDecorator; import net.minecraft.network.chat.ChatType; import net.minecraft.network.chat.Component; @@ -86,6 +39,14 @@ import net.minecraft.network.protocol.status.ServerStatus; import net.minecraft.obfuscate.DontObfuscate; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.bossevents.CustomBossEvents; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.dedicated.DedicatedServerProperties; +import net.minecraft.server.level.*; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.server.level.progress.ChunkProgressListenerFactory; +import net.minecraft.server.network.ServerConnectionListener; +import net.minecraft.server.network.TextFilter; import net.minecraft.server.packs.PackType; import net.minecraft.server.packs.repository.Pack; import net.minecraft.server.packs.repository.PackRepository; @@ -98,25 +59,12 @@ import net.minecraft.server.players.PlayerList; import net.minecraft.server.players.ServerOpListEntry; import net.minecraft.server.players.UserWhiteList; import net.minecraft.tags.TagLoader; -import net.minecraft.util.Crypt; -import net.minecraft.util.CryptException; -import net.minecraft.util.ModCheck; -import net.minecraft.util.Mth; -import net.minecraft.util.NativeModuleLister; -import net.minecraft.util.ProgressListener; -import net.minecraft.util.RandomSource; -import net.minecraft.util.SignatureValidator; -import net.minecraft.util.TimeUtil; +import net.minecraft.util.*; import net.minecraft.util.datafix.DataFixers; import net.minecraft.util.debugchart.RemoteDebugSampleType; import net.minecraft.util.debugchart.SampleLogger; import net.minecraft.util.debugchart.TpsDebugDimensions; -import net.minecraft.util.profiling.EmptyProfileResults; -import net.minecraft.util.profiling.ProfileResults; -import net.minecraft.util.profiling.Profiler; -import net.minecraft.util.profiling.ProfilerFiller; -import net.minecraft.util.profiling.ResultField; -import net.minecraft.util.profiling.SingleTickProfiler; +import net.minecraft.util.profiling.*; import net.minecraft.util.profiling.jfr.JvmProfiler; import net.minecraft.util.profiling.jfr.callback.ProfiledDuration; import net.minecraft.util.profiling.metrics.profiling.ActiveMetricsRecorder; @@ -136,15 +84,7 @@ import net.minecraft.world.flag.FeatureFlagSet; import net.minecraft.world.flag.FeatureFlags; import net.minecraft.world.item.alchemy.PotionBrewing; import net.minecraft.world.item.crafting.RecipeManager; -import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.CustomSpawner; -import net.minecraft.world.level.DataPackConfig; -import net.minecraft.world.level.ForcedChunksSavedData; -import net.minecraft.world.level.GameRules; -import net.minecraft.world.level.GameType; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelSettings; -import net.minecraft.world.level.WorldDataConfiguration; +import net.minecraft.world.level.*; import net.minecraft.world.level.biome.BiomeManager; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.FuelValues; @@ -152,55 +92,46 @@ import net.minecraft.world.level.border.WorldBorder; import net.minecraft.world.level.chunk.storage.ChunkIOErrorReporter; import net.minecraft.world.level.chunk.storage.RegionStorageInfo; import net.minecraft.world.level.dimension.LevelStem; -import net.minecraft.world.level.levelgen.WorldOptions; +import net.minecraft.world.level.levelgen.*; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; -import net.minecraft.world.level.storage.WorldData; -import org.slf4j.Logger; - -// CraftBukkit start -import com.mojang.serialization.Dynamic; -import com.mojang.serialization.Lifecycle; -import java.io.File; -import java.util.Random; -// import jline.console.ConsoleReader; // Paper -import joptsimple.OptionSet; -import net.minecraft.nbt.NbtException; -import net.minecraft.nbt.ReportedNbtException; -import net.minecraft.server.bossevents.CustomBossEvents; -import net.minecraft.server.dedicated.DedicatedServer; -import net.minecraft.server.dedicated.DedicatedServerProperties; -import net.minecraft.server.level.DemoMode; -import net.minecraft.server.level.PlayerRespawnLogic; -import net.minecraft.server.level.ServerChunkCache; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.server.level.ServerPlayerGameMode; -import net.minecraft.server.level.progress.ChunkProgressListener; -import net.minecraft.server.level.progress.ChunkProgressListenerFactory; -import net.minecraft.server.network.ServerConnectionListener; -import net.minecraft.server.network.TextFilter; -import net.minecraft.world.level.levelgen.Heightmap; -import net.minecraft.world.level.levelgen.PatrolSpawner; -import net.minecraft.world.level.levelgen.PhantomSpawner; -import net.minecraft.world.level.levelgen.WorldDimensions; import net.minecraft.world.level.levelgen.presets.WorldPresets; -import net.minecraft.world.level.storage.CommandStorage; -import net.minecraft.world.level.storage.DimensionDataStorage; -import net.minecraft.world.level.storage.LevelData; -import net.minecraft.world.level.storage.LevelDataAndDimensions; -import net.minecraft.world.level.storage.LevelResource; -import net.minecraft.world.level.storage.LevelStorageSource; -import net.minecraft.world.level.storage.LevelSummary; -import net.minecraft.world.level.storage.PlayerDataStorage; -import net.minecraft.world.level.storage.PrimaryLevelData; -import net.minecraft.world.level.storage.ServerLevelData; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import net.minecraft.world.level.storage.*; import net.minecraft.world.level.validation.ContentValidationException; import net.minecraft.world.phys.Vec2; import net.minecraft.world.phys.Vec3; import org.bukkit.Bukkit; import org.bukkit.craftbukkit.CraftRegistry; import org.bukkit.event.server.ServerLoadEvent; +import org.slf4j.Logger; + +import javax.annotation.Nullable; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.net.Proxy; +import java.nio.file.FileStore; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.security.KeyPair; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; // CraftBukkit end @@ -335,6 +266,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping public final Set 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 net.sparklypower.sparklypaper.HalloweenManager halloweenManager = new net.sparklypower.sparklypaper.HalloweenManager(); // SparklyPaper - Spooky month optimizations + // SparklyPaper - parallel world ticking + public java.util.concurrent.Semaphore serverLevelTickingSemaphore = null; + // SparklyPaper end public static S spin(Function serverFactory) { AtomicReference atomicreference = new AtomicReference(); @@ -1865,50 +1799,71 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent - worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent - net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers - worldserver.updateLagCompensationTick(); // Paper - lag compensation + // SparklyPaper start - parallel world ticking + java.util.ArrayDeque> tasks = new java.util.ArrayDeque<>(); + try { + while (iterator.hasNext()) { + ServerLevel worldserver = (ServerLevel) iterator.next(); + worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent + worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent + net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers + worldserver.updateLagCompensationTick(); // Paper - lag compensation - gameprofilerfiller.push(() -> { - String s = String.valueOf(worldserver); + gameprofilerfiller.push(() -> { + String s = String.valueOf(worldserver); - return s + " " + String.valueOf(worldserver.dimension().location()); - }); - /* Drop global time updates - if (this.tickCount % 20 == 0) { - gameprofilerfiller.push("timeSync"); - this.synchronizeTime(worldserver); + return s + " " + String.valueOf(worldserver.dimension().location()); + }); + /* Drop global time updates + if (this.tickCount % 20 == 0) { + gameprofilerfiller.push("timeSync"); + this.synchronizeTime(worldserver); + gameprofilerfiller.pop(); + } + // CraftBukkit end */ + + gameprofilerfiller.push("tick"); + + serverLevelTickingSemaphore.acquire(); + tasks.add( + worldserver.tickExecutor.submit(() -> { + try { + ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread currentThread = (ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread) Thread.currentThread(); + currentThread.currentlyTickingServerLevel = worldserver; + + long i = Util.getNanos(); // SparklyPaper - track world's MSPT + worldserver.tick(shouldKeepTicking); + // SparklyPaper start - track world's MSPT + long j = Util.getNanos() - i; + + // These are from the "tickServer" function + worldserver.tickTimes5s.add(this.tickCount, j); + worldserver.tickTimes10s.add(this.tickCount, j); + worldserver.tickTimes60s.add(this.tickCount, j); + // SparklyPaper end + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Exception ticking world"); + + worldserver.fillReportDetails(crashreport); + throw new ReportedException(crashreport); + } finally { + serverLevelTickingSemaphore.release(); + } + }, worldserver) + ); + + gameprofilerfiller.pop(); gameprofilerfiller.pop(); + worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions } - // CraftBukkit end */ - - gameprofilerfiller.push("tick"); - try { - long i = Util.getNanos(); // SparklyPaper - track world's MSPT - worldserver.tick(shouldKeepTicking); - // SparklyPaper start - track world's MSPT - long j = Util.getNanos() - i; - - // These are from the "tickServer" function - worldserver.tickTimes5s.add(this.tickCount, j); - worldserver.tickTimes10s.add(this.tickCount, j); - worldserver.tickTimes60s.add(this.tickCount, j); - // SparklyPaper end - } catch (Throwable throwable) { - CrashReport crashreport = CrashReport.forThrowable(throwable, "Exception ticking world"); - - worldserver.fillReportDetails(crashreport); - throw new ReportedException(crashreport); + while (!tasks.isEmpty()) { + tasks.pop().get(); } - - gameprofilerfiller.pop(); - gameprofilerfiller.pop(); - worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions + } 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 gameprofilerfiller.popPush("connection"); @@ -2002,6 +1957,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop, ServerLevel> oldLevels = this.levels; Map, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels); newLevels.remove(level.dimension()); + 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/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java index e49e8e98ebbd3c8ba8a784f1626132699209b4e3..d952d42b8faee38f9d55fe578c0308916d31680c 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java @@ -243,6 +243,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface return false; } net.sparklypower.sparklypaper.SparklyPaperCommands.INSTANCE.registerCommands(this); + serverLevelTickingSemaphore = new java.util.concurrent.Semaphore(net.sparklypower.sparklypaper.configs.SparklyPaperConfigUtils.INSTANCE.getConfig().getParallelWorldTicking().getThreads()); // SparklyPaper - parallel world ticking + DedicatedServer.LOGGER.info("Using " + serverLevelTickingSemaphore.availablePermits() + " permits for parallel world ticking"); // SparklyPaper - parallel world ticking // SparklyPaper end // SparklyPaper start - Spooky month optimizations halloweenManager.startHalloweenEpochTask(); diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index 8de30b5dff3dc0e30f1a56303abcb31df8f85c9f..b3917c319c32eb9926418f788faf4adecf40a09a 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -200,7 +200,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe private final MinecraftServer server; public final PrimaryLevelData serverLevelData; // CraftBukkit - type private int lastSpawnChunkRadius; - final EntityTickList entityTickList = new EntityTickList(); + final EntityTickList entityTickList = new EntityTickList(this); // Paper - rewrite chunk system private final GameEventDispatcher gameEventDispatcher; public boolean noSave; @@ -224,6 +224,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe private final StructureCheck structureCheck; private final boolean tickTime; private final RandomSequences randomSequences; + public java.util.concurrent.ExecutorService tickExecutor; // SparklyPaper - parallel world ticking // CraftBukkit start public final LevelStorageSource.LevelStorageAccess convertable; @@ -682,6 +683,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe this.chunkDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController((ServerLevel)(Object)this, this.chunkTaskScheduler); // Paper end - rewrite chunk system this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit + this.tickExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new net.sparklypower.sparklypaper.ServerLevelTickExecutorThreadFactory(getWorld().getName())); // SparklyPaper - parallel world ticking } // Paper start @@ -1251,7 +1253,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } // Paper start - rewrite chunk system if ((++this.tickedBlocksOrFluids & 7L) != 0L) { - ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); + // ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper } // Paper end - rewrite chunk system @@ -1265,7 +1267,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } // Paper start - rewrite chunk system if ((++this.tickedBlocksOrFluids & 7L) != 0L) { - ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); + // ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper } // Paper end - rewrite chunk system @@ -1558,6 +1560,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } private void addPlayer(ServerPlayer player) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add player off-main"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) Entity entity = (Entity) this.getEntities().get(player.getUUID()); if (entity != null) { @@ -1571,7 +1574,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // CraftBukkit start private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { - org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add entity off-main"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) 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/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java index 5a8f396d47577f087abb415c972fd4f51e50faba..f58331b6be3a26e76082d16894d3eb4d5982f318 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -359,6 +359,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple 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 world, GameProfile profile, ClientInformation clientOptions) { super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); @@ -986,6 +987,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple @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; @@ -1629,6 +1631,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple teleportTarget.postTeleportTransition().onTransition(this); return this; } else { + ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot change dimension of a player off-main, from world " + serverLevel().getWorld().getName() + " to world " + worldserver.getWorld().getName()); // SparklyPaper - parallel world ticking (additional concurrency issues logs) // CraftBukkit start /* this.isChangingDimension = true; @@ -1996,6 +1999,12 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple return OptionalInt.empty(); } else { // CraftBukkit start + // SparklyPaper start - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) + if (!hasTickedAtLeastOnceInNewWorld) { + MinecraftServer.LOGGER.warn("Ignoring request to open container " + container + " because we haven't ticked in the current world yet!", new Throwable()); + return OptionalInt.empty(); + } + // SparklyPaper end this.containerMenu = container; if (!this.isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), Objects.requireNonNullElseGet(title, container::getTitle))); // Paper - Add titleOverride to InventoryOpenEvent // CraftBukkit end @@ -2059,6 +2068,11 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple } @Override public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { + // SparklyPaper start - parallel world ticking (debugging) + if (net.sparklypower.sparklypaper.configs.SparklyPaperConfigUtils.INSTANCE.getLogContainerCreationStacktraces()) { + MinecraftServer.LOGGER.warn("Closing " + this.getBukkitEntity().getName() + " inventory that was created at", this.containerMenu.containerCreationStacktrace); + } + // SparklyPaper end CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit // Paper end - Inventory close reason this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java index 30de3d1a7792c38ae946f19cb0e14637919b5001..0ea2d2b32aacdbfca80c0c626520bd863601bbd2 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -137,7 +137,7 @@ public abstract class PlayerList { private static final SimpleDateFormat BAN_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z"); private final MinecraftServer server; public final List players = new java.util.concurrent.CopyOnWriteArrayList(); // CraftBukkit - ArrayList -> CopyOnWriteArrayList: Iterator safety - private final Map playersByUUID = Maps.newHashMap(); + private final Map playersByUUID = Maps.newHashMap(); // SparklyPaper - parallel world ticking (we don't need to replace the original map because we never iterate on top of this map) private final UserBanList bans; private final IpBanList ipBans; private final ServerOpList ops; @@ -158,7 +158,7 @@ public abstract class PlayerList { // CraftBukkit start private CraftServer cserver; - private final Map playersByName = new java.util.HashMap<>(); + private final Map playersByName = new java.util.HashMap<>(); // SparklyPaper - parallel world ticking (we don't need to replace the original map because we never iterate on top of this map) public @Nullable String collideRuleTeamName; // Paper - Configurable player collision public PlayerList(MinecraftServer server, LayeredRegistryAccess registryManager, PlayerDataStorage saveHandler, int maxPlayers) { @@ -182,6 +182,7 @@ public abstract class PlayerList { abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) { + 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(); @@ -769,6 +770,13 @@ public abstract class PlayerList { } public ServerPlayer respawn(ServerPlayer entityplayer, boolean flag, Entity.RemovalReason entity_removalreason, RespawnReason reason, Location location) { + System.out.println("respawning player - current player container is " + entityplayer.containerMenu + " but their inventory is " + entityplayer.inventoryMenu); + // SparklyPaper - parallel world ticking (additional concurrency issues logs) + if (location != null) + ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, from world " + entityplayer.serverLevel().getWorld().getName() + " to world " + location.getWorld().getName()); + else + ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, respawning in world " + entityplayer.serverLevel().getWorld().getName()); + // SparklyPaper end entityplayer.stopRiding(); // CraftBukkit this.players.remove(entityplayer); this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot @@ -781,6 +789,7 @@ public abstract class PlayerList { ServerPlayer entityplayer1 = entityplayer; Level fromWorld = entityplayer.level(); entityplayer.wonGame = false; + entityplayer.hasTickedAtLeastOnceInNewWorld = false; // SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) // CraftBukkit end entityplayer1.connection = entityplayer.connection; diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java index a15546e433ebba6c0de01bdaaef201a3d99a87b5..3e440c7ce88d7ebd50eba488c629bfa0bd0d242f 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -882,7 +882,7 @@ 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 (false && !(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities // SparklyPaper - parallel world ticking (see issue #9, this is executed in the server level tick for non-player entities) this.handlePortal(); } } @@ -3986,6 +3986,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } private Entity teleportCrossDimension(ServerLevel world, TeleportTransition teleportTarget) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(world, "Cannot teleport entity to another world off-main, from world " + level.getWorld().getName() + " to world " + world.getWorld().getName()); // SparklyPaper - parallel world ticking (additional concurrency issues logs) List list = this.getPassengers(); List list1 = new ArrayList(list.size()); diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java index 4680f77a275d8d2b226018db89a571ac25998dd8..42b00ada65878545e464aa169698288982d46def 100644 --- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java @@ -106,8 +106,14 @@ public abstract class AbstractContainerMenu { this.title = title; } // CraftBukkit end + public Throwable containerCreationStacktrace; // SparklyPaper - parallel world ticking (debugging) protected AbstractContainerMenu(@Nullable MenuType type, int syncId) { + // SparklyPaper - parallel world ticking (debugging) + if (net.sparklypower.sparklypaper.configs.SparklyPaperConfigUtils.INSTANCE.getLogContainerCreationStacktraces()) { + this.containerCreationStacktrace = new Throwable(); + } + // SparklyPaper end this.carried = ItemStack.EMPTY; this.remoteSlots = NonNullList.create(); this.remoteDataSlots = new IntArrayList(); diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java index 33e7d2884195677c4d6340d8b84c1dd85c636ec1..02a719aaf7c860efbb51aed7d21cc83e14aef602 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java @@ -445,8 +445,8 @@ public final class ItemStack implements DataComponentHolder { if (enuminteractionresult.consumesAction() && world.captureTreeGeneration && world.capturedBlockStates.size() > 0) { world.captureTreeGeneration = false; Location location = CraftLocation.toBukkit(blockposition, world.getWorld()); - TreeType treeType = SaplingBlock.treeType; - SaplingBlock.treeType = null; + TreeType treeType = SaplingBlock.treeTypeRT.get(); // SparklyPaper - parallel world ticking + SaplingBlock.treeTypeRT.set(null); // SparklyPaper - parallel world ticking List blocks = new java.util.ArrayList<>(world.capturedBlockStates.values()); world.capturedBlockStates.clear(); StructureGrowEvent structureEvent = null; diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java index 39d5633ae7cdb8609f5840de5ba26237d501d8a2..856aab98edbd67c68d33e529d0199c0506b11da8 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -175,6 +175,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray public net.sparklypower.sparklypaper.configs.SparklyPaperConfig.SparklyPaperWorldConfig sparklyPaperConfig; // SparklyPaper + public com.destroystokyo.paper.util.RedstoneWireTurbo turbo = new com.destroystokyo.paper.util.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; @@ -1088,6 +1089,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl @Override public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) { + 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 @@ -1487,7 +1489,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl tickingblockentity.tick(); // Paper start - rewrite chunk system if ((++tickedEntities & 7) == 0) { - ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks(); + // ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks(); // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) } // Paper end - rewrite chunk system } // SparklyPaper end @@ -1511,7 +1513,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Paper end - Prevent block entity and entity crashes } - this.moonrise$midTickTasks(); // Paper - rewrite chunk system + // this.moonrise$midTickTasks(); // Paper - rewrite chunk system // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) } // Paper start - Option to prevent armor stands from doing entity lookups @Override @@ -1569,6 +1571,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl @Nullable public BlockEntity getBlockEntity(BlockPos blockposition, boolean validate) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThreadOrAsyncThread((ServerLevel) this, "Cannot read world asynchronously"); // SparklyPaper start - 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(blockposition)) != null) { @@ -1580,6 +1583,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl } public void setBlockEntity(BlockEntity blockEntity) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel) this, "Cannot modify world asynchronously"); // SparklyPaper start - parallel world ticking BlockPos blockposition = blockEntity.getBlockPos(); if (!this.isOutsideBuildHeight(blockposition)) { @@ -1665,6 +1669,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl @Override public List getEntities(@Nullable Entity except, AABB box, Predicate predicate) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, box, "Cannot getEntities asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) Profiler.get().incrementCounter("getEntities"); List list = Lists.newArrayList(); @@ -1991,8 +1996,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl public abstract RecipeAccess recipeAccess(); public BlockPos getBlockRandomPos(int x, int y, int z, int l) { - this.randValue = this.randValue * 3 + 1013904223; - int i1 = this.randValue >> 2; + int i1 = this.random.nextInt() >> 2; // SparklyPaper - parallel world ticking return new BlockPos(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); } diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java index 5a6c153fa2873aecba0d0d02be2cc2a514f445e3..1f8f05c02072cdfc69be0b2d7d33fd9d363a9539 100644 --- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java @@ -52,7 +52,8 @@ public class DispenserBlock extends BaseEntityBlock { private static final DefaultDispenseItemBehavior DEFAULT_BEHAVIOR = new DefaultDispenseItemBehavior(); public static final Map DISPENSER_REGISTRY = new IdentityHashMap(); private static final int TRIGGER_DURATION = 4; - public static boolean eventFired = false; // CraftBukkit + // public static boolean eventFired = false; // CraftBukkit // SparklyPaper - parallel world ticking + public static ThreadLocal eventFired = ThreadLocal.withInitial(() -> Boolean.FALSE); // SparklyPaper - parallel world ticking @Override public MapCodec codec() { @@ -109,7 +110,7 @@ public class DispenserBlock extends BaseEntityBlock { if (idispensebehavior != DispenseItemBehavior.NOOP) { if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - Add BlockPreDispenseEvent - DispenserBlock.eventFired = false; // CraftBukkit - reset event status + DispenserBlock.eventFired.set(Boolean.FALSE); // CraftBukkit - reset event status // SparklyPaper - parallel world ticking tileentitydispenser.setItem(i, idispensebehavior.dispense(sourceblock, itemstack)); } diff --git a/src/main/java/net/minecraft/world/level/block/FungusBlock.java b/src/main/java/net/minecraft/world/level/block/FungusBlock.java index ba5fff8892593a921f57cd55ad60cd6744ab4cf1..21b11b120a240ac39ef5d18a3401cb268dbfe6b1 100644 --- a/src/main/java/net/minecraft/world/level/block/FungusBlock.java +++ b/src/main/java/net/minecraft/world/level/block/FungusBlock.java @@ -76,9 +76,9 @@ public class FungusBlock extends BushBlock implements BonemealableBlock { this.getFeature(world).ifPresent((holder) -> { // CraftBukkit start if (this == Blocks.WARPED_FUNGUS) { - SaplingBlock.treeType = org.bukkit.TreeType.WARPED_FUNGUS; + SaplingBlock.treeTypeRT.set(org.bukkit.TreeType.WARPED_FUNGUS); // SparklyPaper - parallel world ticking } else if (this == Blocks.CRIMSON_FUNGUS) { - SaplingBlock.treeType = org.bukkit.TreeType.CRIMSON_FUNGUS; + SaplingBlock.treeTypeRT.set(org.bukkit.TreeType.CRIMSON_FUNGUS); // SparklyPaper - parallel world ticking } // CraftBukkit end ((ConfiguredFeature) holder.value()).place(world, world.getChunkSource().getGenerator(), random, pos); diff --git a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java index 7900856c9e86afe907c00c9ac31bec93ece50abe..4e6adc7a8fd97613cb75d40fa38d504494bbe53b 100644 --- a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java +++ b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java @@ -105,7 +105,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock { return false; } else { world.removeBlock(pos, false); - SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? TreeType.BROWN_MUSHROOM : TreeType.RED_MUSHROOM; // CraftBukkit + SaplingBlock.treeTypeRT.set((this == Blocks.BROWN_MUSHROOM) ? TreeType.BROWN_MUSHROOM : TreeType.RED_MUSHROOM); // CraftBukkit // SparklyPaper - parallel world ticking if (((ConfiguredFeature) ((Holder) optional.get()).value()).place(world, world.getChunkSource().getGenerator(), random, pos)) { return true; } else { diff --git a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java index 21f2c61023fadcce30452a02f067cd5d87e5d8dc..056bd2cb7898f691940cdcb85d124948ac49c7d1 100644 --- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java +++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java @@ -292,7 +292,7 @@ public class RedStoneWireBlock extends Block { // Paper start - Optimize redstone (Eigencraft) // The bulk of the new functionality is found in RedstoneWireTurbo.java - com.destroystokyo.paper.util.RedstoneWireTurbo turbo = new com.destroystokyo.paper.util.RedstoneWireTurbo(this); + // com.destroystokyo.paper.util.RedstoneWireTurbo turbo = new com.destroystokyo.paper.util.RedstoneWireTurbo(this); // SparklyPaper - parallel world ticking (moved to world) /* * Modified version of pre-existing updateSurroundingRedstone, which is called from @@ -308,7 +308,7 @@ public class RedStoneWireBlock extends Block { if (orientation != null) { source = pos.relative(orientation.getFront().getOpposite()); } - turbo.updateSurroundingRedstone(worldIn, pos, state, source); + worldIn.turbo.updateSurroundingRedstone(worldIn, pos, state, source); // turbo.updateSurroundingRedstone(worldIn, pos, state, source); // SparklyPaper - parallel world ticking return; } updatePowerStrength(worldIn, pos, state, orientation, blockAdded); @@ -336,7 +336,7 @@ 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); + level.turbo.updateNeighborShapes(level, pos, state); } } } diff --git a/src/main/java/net/minecraft/world/level/block/SaplingBlock.java b/src/main/java/net/minecraft/world/level/block/SaplingBlock.java index d262a5a6da57ef9ba9a6fe0dfbc88f577105e74f..15db3fb971a944e856b3cde86035d290673c0c1a 100644 --- a/src/main/java/net/minecraft/world/level/block/SaplingBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SaplingBlock.java @@ -35,7 +35,7 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { protected static final float AABB_OFFSET = 6.0F; protected static final VoxelShape SHAPE = Block.box(2.0D, 0.0D, 2.0D, 14.0D, 12.0D, 14.0D); protected final TreeGrower treeGrower; - public static TreeType treeType; // CraftBukkit + public static final ThreadLocal treeTypeRT = new ThreadLocal<>(); // CraftBukkit // SparklyPaper - parallel world ticking (from Folia) @Override public MapCodec codec() { @@ -73,8 +73,8 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock { this.treeGrower.growTree(world, world.getChunkSource().getGenerator(), pos, state, random); world.captureTreeGeneration = false; if (world.capturedBlockStates.size() > 0) { - TreeType treeType = SaplingBlock.treeType; - SaplingBlock.treeType = null; + TreeType treeType = SaplingBlock.treeTypeRT.get(); // SparklyPaper - parallel world ticking + SaplingBlock.treeTypeRT.set(null); // SparklyPaper - parallel world ticking Location location = CraftLocation.toBukkit(pos, world.getWorld()); java.util.List blocks = new java.util.ArrayList<>(world.capturedBlockStates.values()); world.capturedBlockStates.clear(); diff --git a/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java index 1f29b2419914ca9257db6553f01b7e7ec49bfc18..8297c5be8c35fc6762c433aa072a78bf5fd666da 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java @@ -82,6 +82,12 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co return canUnlock(player, lock, containerName, null); } public static boolean canUnlock(Player player, LockCode lock, Component containerName, @Nullable BlockEntity blockEntity) { + // SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch) + if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != serverPlayer.serverLevel()) { + 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(containerName)); diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java index 4729befa12732a9fd65cce243b33b3b479026c41..a8113ee6b5a2e74182ba973231912ca636db5039 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java @@ -46,9 +46,9 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi // Paper end - Fix NPE in SculkBloomEvent world access public static void serverTick(Level world, BlockPos pos, BlockState state, SculkCatalystBlockEntity blockEntity) { - org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = blockEntity.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.sourceBlockOverrideRT.set(blockEntity.getBlockPos()); // SparklyPaper - parallel world ticking // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. blockEntity.catalystListener.getSculkSpreader().updateCursors(world, pos, world.getRandom(), true); - org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = null; // CraftBukkit + org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverrideRT.set(null); // SparklyPaper - parallel world ticking // CraftBukkit } @Override diff --git a/src/main/java/net/minecraft/world/level/block/grower/TreeGrower.java b/src/main/java/net/minecraft/world/level/block/grower/TreeGrower.java index 2fec480b6ca12a95013d920852db58722579caf1..5566c6a2992ac3aa0a9f9cd62f87d401c0a73179 100644 --- a/src/main/java/net/minecraft/world/level/block/grower/TreeGrower.java +++ b/src/main/java/net/minecraft/world/level/block/grower/TreeGrower.java @@ -175,55 +175,57 @@ public final class TreeGrower { // CraftBukkit start private void setTreeType(Holder> holder) { ResourceKey> worldgentreeabstract = holder.unwrapKey().get(); + TreeType treeType; // SparklyPaper - parallel world ticking if (worldgentreeabstract == TreeFeatures.OAK || worldgentreeabstract == TreeFeatures.OAK_BEES_005) { - SaplingBlock.treeType = TreeType.TREE; + treeType = TreeType.TREE; } else if (worldgentreeabstract == TreeFeatures.HUGE_RED_MUSHROOM) { - SaplingBlock.treeType = TreeType.RED_MUSHROOM; + treeType = TreeType.RED_MUSHROOM; } else if (worldgentreeabstract == TreeFeatures.HUGE_BROWN_MUSHROOM) { - SaplingBlock.treeType = TreeType.BROWN_MUSHROOM; + treeType = TreeType.BROWN_MUSHROOM; } else if (worldgentreeabstract == TreeFeatures.JUNGLE_TREE) { - SaplingBlock.treeType = TreeType.COCOA_TREE; + treeType = TreeType.COCOA_TREE; } else if (worldgentreeabstract == TreeFeatures.JUNGLE_TREE_NO_VINE) { - SaplingBlock.treeType = TreeType.SMALL_JUNGLE; + treeType = TreeType.SMALL_JUNGLE; } else if (worldgentreeabstract == TreeFeatures.PINE) { - SaplingBlock.treeType = TreeType.TALL_REDWOOD; + treeType = TreeType.TALL_REDWOOD; } else if (worldgentreeabstract == TreeFeatures.SPRUCE) { - SaplingBlock.treeType = TreeType.REDWOOD; + treeType = TreeType.REDWOOD; } else if (worldgentreeabstract == TreeFeatures.ACACIA) { - SaplingBlock.treeType = TreeType.ACACIA; + treeType = TreeType.ACACIA; } else if (worldgentreeabstract == TreeFeatures.BIRCH || worldgentreeabstract == TreeFeatures.BIRCH_BEES_005) { - SaplingBlock.treeType = TreeType.BIRCH; + treeType = TreeType.BIRCH; } else if (worldgentreeabstract == TreeFeatures.SUPER_BIRCH_BEES_0002) { - SaplingBlock.treeType = TreeType.TALL_BIRCH; + treeType = TreeType.TALL_BIRCH; } else if (worldgentreeabstract == TreeFeatures.SWAMP_OAK) { - SaplingBlock.treeType = TreeType.SWAMP; + treeType = TreeType.SWAMP; } else if (worldgentreeabstract == TreeFeatures.FANCY_OAK || worldgentreeabstract == TreeFeatures.FANCY_OAK_BEES_005) { - SaplingBlock.treeType = TreeType.BIG_TREE; + treeType = TreeType.BIG_TREE; } else if (worldgentreeabstract == TreeFeatures.JUNGLE_BUSH) { - SaplingBlock.treeType = TreeType.JUNGLE_BUSH; + treeType = TreeType.JUNGLE_BUSH; } else if (worldgentreeabstract == TreeFeatures.DARK_OAK) { - SaplingBlock.treeType = TreeType.DARK_OAK; + treeType = TreeType.DARK_OAK; } else if (worldgentreeabstract == TreeFeatures.MEGA_SPRUCE) { - SaplingBlock.treeType = TreeType.MEGA_REDWOOD; + treeType = TreeType.MEGA_REDWOOD; } else if (worldgentreeabstract == TreeFeatures.MEGA_PINE) { - SaplingBlock.treeType = TreeType.MEGA_PINE; + treeType = TreeType.MEGA_PINE; } else if (worldgentreeabstract == TreeFeatures.MEGA_JUNGLE_TREE) { - SaplingBlock.treeType = TreeType.JUNGLE; + treeType = TreeType.JUNGLE; } else if (worldgentreeabstract == TreeFeatures.AZALEA_TREE) { - SaplingBlock.treeType = TreeType.AZALEA; + treeType = TreeType.AZALEA; } else if (worldgentreeabstract == TreeFeatures.MANGROVE) { - SaplingBlock.treeType = TreeType.MANGROVE; + treeType = TreeType.MANGROVE; } else if (worldgentreeabstract == TreeFeatures.TALL_MANGROVE) { - SaplingBlock.treeType = TreeType.TALL_MANGROVE; + treeType = TreeType.TALL_MANGROVE; } else if (worldgentreeabstract == TreeFeatures.CHERRY || worldgentreeabstract == TreeFeatures.CHERRY_BEES_005) { - SaplingBlock.treeType = TreeType.CHERRY; + treeType = TreeType.CHERRY; } else if (worldgentreeabstract == TreeFeatures.PALE_OAK) { - SaplingBlock.treeType = TreeType.PALE_OAK; + treeType = TreeType.PALE_OAK; } else if (worldgentreeabstract == TreeFeatures.PALE_OAK_CREAKING) { - SaplingBlock.treeType = TreeType.PALE_OAK_CREAKING; + treeType = TreeType.PALE_OAK_CREAKING; } else { throw new IllegalArgumentException("Unknown tree generator " + worldgentreeabstract); } + SaplingBlock.treeTypeRT.set(treeType); // SparklyPaper - parallel world ticking } // CraftBukkit end diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java index b4c16fe7c9215b5610b7e7488c29b497b5357ecc..9771f4ff6083868442fa142ef5c6c9a31397407f 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -382,6 +382,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p @Nullable public BlockState setBlockState(BlockPos blockposition, BlockState iblockdata, boolean flag, boolean doPlace) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, blockposition, "Updating block asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) // CraftBukkit end int i = blockposition.getY(); LevelChunkSection chunksection = this.getSection(this.getSectionIndex(i)); diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java index d8b4196adf955f8d414688dc451caac2d9c609d9..56089483da1313d2da90c1921af33c2d81024680 100644 --- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java +++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java @@ -10,17 +10,26 @@ import net.minecraft.world.entity.Entity; public class EntityTickList { private final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system + // SparklyPaper start - parallel world ticking + // Used to track async entity additions/removals/loops + private final net.minecraft.server.level.ServerLevel serverLevel; + public EntityTickList(net.minecraft.server.level.ServerLevel serverLevel) { + this.serverLevel = serverLevel; + } + // SparklyPaper end private void ensureActiveIsNotIterated() { // Paper - rewrite chunk system } public void add(Entity entity) { + 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) { + 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,7 +39,7 @@ public class EntityTickList { } public void forEach(Consumer action) { - // Paper start - rewrite chunk system + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverLevel, "Asynchronous entity ticklist iteration"); // Paper // SparklyPaper - parallel world ticking (additional concurrency issues logs) // 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) final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet.Iterator iterator = this.entities.iterator(); diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapIndex.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapIndex.java index 84f4664950e0cef7bd823bfc74f37cefce620d9e..b0c3b64da7aed676a5614c442948bbe96c3d2d73 100644 --- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapIndex.java +++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapIndex.java @@ -34,17 +34,21 @@ public class MapIndex extends SavedData { @Override public CompoundTag save(CompoundTag nbt, HolderLookup.Provider registries) { + synchronized (this.usedAuxIds) { // SparklyPaper start - make map data thread-safe for (Entry entry : this.usedAuxIds.object2IntEntrySet()) { - nbt.putInt(entry.getKey(), entry.getIntValue()); + nbt.putInt(entry.getKey(), entry.getIntValue()); } + } // SparklyPaper end - make map data thread-safe return nbt; } public MapId getFreeAuxValueForMap() { + 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/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 8f234b46c24a0ae08aa5f8190c5b27e1f62dfbab..12f780b349e825747c2441d9636ae1dc667312e5 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -458,7 +458,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { } private boolean unloadChunk0(int x, int z, boolean save) { - org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot unload chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) if (!this.isChunkLoaded(x, z)) { return true; } @@ -481,6 +481,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { if (!unloadChunk0(x, z, false)) { return false; } + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot regenerate chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) warnUnsafeChunk("regenerating a faraway chunk", x, z); // Paper final long chunkKey = ChunkCoordIntPair.pair(x, z); @@ -502,6 +503,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean refreshChunk(int x, int z) { + 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; @@ -562,7 +564,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean loadChunk(int x, int z, boolean generate) { - org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot + 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) 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 @@ -814,6 +816,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { + 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); @@ -929,6 +932,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { } public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source, Consumer configurator) { // Paper end - expand explosion API + 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 @@ -1020,6 +1024,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { + 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); @@ -1050,6 +1055,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void setBiome(int x, int y, int z, Holder bb) { BlockPos pos = new BlockPos(x, 0, z); + 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); @@ -2322,6 +2328,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { + 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 5cb69d0b822e11a99a96aef4f59986d083b079f4..78f9c3f3b068f638570942961ad41969abc9c028 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java @@ -75,6 +75,11 @@ public class CraftBlock implements Block { } public net.minecraft.world.level.block.state.BlockState getNMS() { + // Folia start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + 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 +162,11 @@ public class CraftBlock implements Block { } private void setData(final byte data, int flag) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // SparklyPaper end - parallel world ticking this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flag); } @@ -198,6 +208,12 @@ public class CraftBlock implements Block { } public static boolean setTypeAndData(LevelAccessor world, BlockPos position, net.minecraft.world.level.block.state.BlockState old, net.minecraft.world.level.block.state.BlockState blockData, boolean applyPhysics) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // SparklyPaper end - parallel world ticking + // SPIGOT-611: need to do this to prevent glitchiness. Easier to handle this here (like /setblock) than to fix weirdness in tile entity cleanup if (old.hasBlockEntity() && blockData.getBlock() != old.getBlock()) { // SPIGOT-3725 remove old tile entity if block changes // SPIGOT-4612: faster - just clear tile @@ -343,18 +359,33 @@ public class CraftBlock implements Block { @Override public Biome getBiome() { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + 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 (world instanceof ServerLevel serverWorld) { + 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 (world instanceof ServerLevel serverWorld) { + 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); } @@ -402,6 +433,11 @@ public class CraftBlock implements Block { @Override public boolean isBlockFaceIndirectlyPowered(BlockFace face) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // SparklyPaper end - parallel world ticking int power = this.world.getMinecraftWorld().getSignal(this.position, CraftBlock.blockFaceToNotch(face)); Block relative = this.getRelative(face); @@ -414,6 +450,11 @@ public class CraftBlock implements Block { @Override public int getBlockPower(BlockFace face) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // SparklyPaper end - parallel world ticking int power = 0; net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); int x = this.getX(); @@ -484,6 +525,11 @@ public class CraftBlock implements Block { @Override public boolean breakNaturally() { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // SparklyPaper end - parallel world ticking return this.breakNaturally(null); } @@ -543,6 +589,11 @@ public class CraftBlock implements Block { @Override public boolean applyBoneMeal(BlockFace face) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // SparklyPaper end - parallel world ticking Direction direction = CraftBlock.blockFaceToNotch(face); BlockFertilizeEvent event = null; ServerLevel world = this.getCraftWorld().getHandle(); @@ -554,8 +605,8 @@ public class CraftBlock implements Block { world.captureTreeGeneration = false; if (world.capturedBlockStates.size() > 0) { - TreeType treeType = SaplingBlock.treeType; - SaplingBlock.treeType = null; + TreeType treeType = SaplingBlock.treeTypeRT.get(); // SparklyPaper - parallel world ticking + SaplingBlock.treeTypeRT.set(null); // SparklyPaper - parallel world ticking List blocks = new ArrayList<>(world.capturedBlockStates.values()); world.capturedBlockStates.clear(); StructureGrowEvent structureEvent = null; @@ -644,6 +695,11 @@ public class CraftBlock implements Block { @Override public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // SparklyPaper end - parallel world ticking Preconditions.checkArgument(start != null, "Location start cannot be null"); Preconditions.checkArgument(this.getWorld().equals(start.getWorld()), "Location start cannot be a different world"); start.checkFinite(); @@ -685,6 +741,11 @@ public class CraftBlock implements Block { @Override public boolean canPlace(BlockData data) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // SparklyPaper end - parallel world ticking Preconditions.checkArgument(data != null, "BlockData cannot be null"); net.minecraft.world.level.block.state.BlockState iblockdata = ((CraftBlockData) data).getState(); net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); @@ -719,6 +780,11 @@ public class CraftBlock implements Block { @Override public void tick() { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + 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 04ae258a2f8e98421340d29d5cceedec045171b7..3e90f16d303a8f3d1e2b01e7cbd62cbd991d3f06 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java @@ -25,7 +25,7 @@ public abstract class CraftBlockEntityState extends Craft private final T tileEntity; private final T snapshot; public boolean snapshotDisabled; // Paper - public static boolean DISABLE_SNAPSHOT = false; // Paper + public static ThreadLocal DISABLE_SNAPSHOT = ThreadLocal.withInitial(() -> Boolean.FALSE); // SparklyPaper - parallel world ticking public CraftBlockEntityState(World world, T tileEntity) { super(world, tileEntity.getBlockPos(), tileEntity.getBlockState()); @@ -34,8 +34,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 = DISABLE_SNAPSHOT.get(); // SparklyPaper - parallel world ticking + if (DISABLE_SNAPSHOT.get()) { // SparklyPaper - parallel world ticking 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..9856860ee2987738bbcad5d752670e30f569ba74 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java @@ -215,6 +215,12 @@ public class CraftBlockState implements BlockState { LevelAccessor access = this.getWorldHandle(); CraftBlock block = this.getBlock(); + // SparklyPaper start - parallel world ticking + if (access instanceof net.minecraft.server.level.ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // SparklyPaper end - parallel world ticking + if (block.getType() != this.getType()) { if (!force) { return false; @@ -350,6 +356,7 @@ public class CraftBlockState implements BlockState { @Override public java.util.Collection getDrops(org.bukkit.inventory.ItemStack item, org.bukkit.entity.Entity entity) { + 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 56453454cbd4b9e9270fc833f8ab38d5fa7a3763..ad507c36d89ce8639fbedbb1df0da66bdc96affa 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.DISABLE_SNAPSHOT.get(); // SparklyPaper - parallel world ticking + CraftBlockEntityState.DISABLE_SNAPSHOT.set(!useSnapshot); // SparklyPaper - parallel world ticking 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.DISABLE_SNAPSHOT.set(prev); // SparklyPaper - parallel world ticking } // 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 85926a55066ac793b2e4cfe3502f9ab201df91a3..122bcd352c01ba1a635c34a148bab25548301ee6 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -951,7 +951,7 @@ public class CraftEventFactory { return CraftEventFactory.handleBlockSpreadEvent(world, source, target, block, 2); } - 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<>(); // 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 (this is from Folia, fixes concurrency bugs with sculk catalysts) public static boolean handleBlockSpreadEvent(LevelAccessor world, BlockPos source, BlockPos target, net.minecraft.world.level.block.state.BlockState block, int flag) { // Suppress during worldgen @@ -963,7 +963,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.sourceBlockOverrideRT.get() != null ? CraftEventFactory.sourceBlockOverrideRT.get() : source), state); // SparklyPaper - parallel world ticking Bukkit.getPluginManager().callEvent(event); if (!event.isCancelled()) { @@ -2247,7 +2247,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.eventFired.get()) { // SparklyPaper - parallel world ticking if (!event.callEvent()) { return itemStack; } diff --git a/src/main/kotlin/net/sparklypower/sparklypaper/ServerLevelTickExecutorThreadFactory.kt b/src/main/kotlin/net/sparklypower/sparklypaper/ServerLevelTickExecutorThreadFactory.kt new file mode 100644 index 0000000000000000000000000000000000000000..9a40afbd7e5085179dc016c900ecb60e98250daf --- /dev/null +++ b/src/main/kotlin/net/sparklypower/sparklypaper/ServerLevelTickExecutorThreadFactory.kt @@ -0,0 +1,20 @@ +package net.sparklypower.sparklypaper + +import ca.spottedleaf.moonrise.common.util.TickThread +import java.util.concurrent.ThreadFactory + +class ServerLevelTickExecutorThreadFactory(private val worldName: String) : ThreadFactory { + override fun newThread(p0: Runnable): Thread { + val tickThread = TickThread.ServerLevelTickThread(p0, "serverlevel-tick-worker [$worldName]") + + if (tickThread.isDaemon) { + tickThread.isDaemon = false + } + + if (tickThread.priority != 5) { + tickThread.priority = 5 + } + + return tickThread + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/sparklypower/sparklypaper/configs/SparklyPaperConfigUtils.kt b/src/main/kotlin/net/sparklypower/sparklypaper/configs/SparklyPaperConfigUtils.kt index 072b5811acf9ecc58e3bba7f74dc0dcf8556e848..ddb2fec6ff1604577705477237bdc6a7b2702328 100644 --- a/src/main/kotlin/net/sparklypower/sparklypaper/configs/SparklyPaperConfigUtils.kt +++ b/src/main/kotlin/net/sparklypower/sparklypaper/configs/SparklyPaperConfigUtils.kt @@ -23,6 +23,7 @@ object SparklyPaperConfigUtils { ) lateinit var config: SparklyPaperConfig + val logContainerCreationStacktraces = java.lang.Boolean.getBoolean("sparklypaper.logContainerCreationStacktraces") fun init(configFile: File) { // Write default config if the file doesn't exist