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/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java index 6bc7c6f16a1649fc9e24e7cf90fca401e5bd4875..a6082e925ad1e27d6a5089356af1840ee8852a9a 100644 --- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java +++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java @@ -1024,7 +1024,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) { @@ -1053,7 +1053,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"); @@ -1328,14 +1328,14 @@ public final class ChunkHolderManager { } private boolean processTicketUpdates(final boolean checkLocks, final boolean processFullUpdates, List scheduledTasks) { - TickThread.ensureTickThread("Cannot process ticket levels off-main"); + TickThread.ensureTickThread(world, "Cannot process ticket levels off-main"); // SparklyPaper - parallel world ticking if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { throw new IllegalStateException("Cannot update ticket level while unloading chunks or updating entity manager"); } 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/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java index 17ce14f2dcbf900890efbc2351782bc6f8867068..3d762963d9b7bbb355358ccdb453a32c3651bc51 100644 --- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java +++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java @@ -330,7 +330,7 @@ public final class ChunkTaskScheduler { public void scheduleTickingState(final int chunkX, final int chunkZ, final FullChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer onComplete) { - if (!TickThread.isTickThread()) { + if (!TickThread.isTickThreadFor(world, chunkX, chunkZ)) { // SparklyPaper - parallel world ticking (other threads may call this method, such as the container stillValid check, which may trigger a chunk load in a different thread) this.scheduleChunkTask(chunkX, chunkZ, () -> { ChunkTaskScheduler.this.scheduleTickingState(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); }, priority); @@ -486,7 +486,7 @@ public final class ChunkTaskScheduler { public void scheduleChunkLoad(final int chunkX, final int chunkZ, final ChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer onComplete) { - if (!TickThread.isTickThread()) { + if (!TickThread.isTickThreadFor(world, chunkX, chunkZ)) { // SparklyPaper - parallel world ticking this.scheduleChunkTask(chunkX, chunkZ, () -> { ChunkTaskScheduler.this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); }, priority); diff --git a/src/main/java/io/papermc/paper/util/TickThread.java b/src/main/java/io/papermc/paper/util/TickThread.java index f9063e2282f89e97a378f06822cde0a64ab03f9a..98abe711f63c0a112ef969bd74bda81f2a72ed82 100644 --- a/src/main/java/io/papermc/paper/util/TickThread.java +++ b/src/main/java/io/papermc/paper/util/TickThread.java @@ -17,6 +17,7 @@ import java.util.concurrent.atomic.AtomicInteger; public class TickThread extends Thread { public static final boolean STRICT_THREAD_CHECKS = Boolean.getBoolean("paper.strict-thread-checks"); + 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 static { if (STRICT_THREAD_CHECKS) { @@ -41,50 +42,93 @@ public class TickThread extends Thread { @Deprecated public static void ensureTickThread(final String reason) { if (!isTickThread()) { - MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); - throw new IllegalStateException(reason); + MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " - " + getTickThreadInformation(), new Throwable()); + if (HARD_THROW) + throw new IllegalStateException(reason); } } public static void ensureTickThread(final ServerLevel world, final BlockPos pos, final String reason) { if (!isTickThreadFor(world, pos)) { - MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); - throw new IllegalStateException(reason); + MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " blockPos: " + pos + " - " + getTickThreadInformation(), new Throwable()); + if (HARD_THROW) + throw new IllegalStateException(reason); } } public static void ensureTickThread(final ServerLevel world, final ChunkPos pos, final String reason) { if (!isTickThreadFor(world, pos)) { - MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); - throw new IllegalStateException(reason); + MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " chunkPos: " + pos + " - " + getTickThreadInformation(), new Throwable()); + if (HARD_THROW) + throw new IllegalStateException(reason); } } public static void ensureTickThread(final ServerLevel world, final int chunkX, final int chunkZ, final String reason) { if (!isTickThreadFor(world, chunkX, chunkZ)) { - MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); - throw new IllegalStateException(reason); + MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " chunkX: " + chunkX + " chunkZ: " + chunkZ + " - " + getTickThreadInformation(), new Throwable()); + if (HARD_THROW) + throw new IllegalStateException(reason); } } public static void ensureTickThread(final Entity entity, final String reason) { if (!isTickThreadFor(entity)) { - MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); - throw new IllegalStateException(reason); + MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ entity " + entity.getStringUUID() + " - " + getTickThreadInformation(), new Throwable()); + if (HARD_THROW) + throw new IllegalStateException(reason); } } public static void ensureTickThread(final ServerLevel world, final AABB aabb, final String reason) { if (!isTickThreadFor(world, aabb)) { - MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); - throw new IllegalStateException(reason); + MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " aabb: " + aabb + " - " + getTickThreadInformation(), new Throwable()); + if (HARD_THROW) + throw new IllegalStateException(reason); } } public static void ensureTickThread(final ServerLevel world, final double blockX, final double blockZ, final String reason) { if (!isTickThreadFor(world, blockX, blockZ)) { - MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); - throw new IllegalStateException(reason); + MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " blockX: " + blockX + " blockZ: " + blockZ + " - " + getTickThreadInformation(), 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 ServerLevel world, final String reason) { + if (!isTickThreadFor(world)) { + MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " - " + getTickThreadInformation(), 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) { + MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread ONLY tick thread check: " + reason + " - " + getTickThreadInformation(), 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 ServerLevel world, final String reason) { + boolean isValidTickThread = isTickThreadFor(world); + boolean isAsyncThread = !isTickThread(); + boolean isValid = isAsyncThread || isValidTickThread; + if (!isValid) { + MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread or async thread check: " + reason + " @ world " + world.getWorld().getName() + " - " + getTickThreadInformation(), new Throwable()); + if (HARD_THROW) + throw new IllegalStateException(reason); } } @@ -109,6 +153,32 @@ public class TickThread extends Thread { return (TickThread)Thread.currentThread(); } + public static String getTickThreadInformation() { + 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.getServer().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 static boolean isTickThread() { return Thread.currentThread() instanceof TickThread; } @@ -118,42 +188,111 @@ public class TickThread extends Thread { } public static boolean isTickThreadFor(final ServerLevel 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 ServerLevel 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 ServerLevel 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 ServerLevel 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 ServerLevel 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 ServerLevel 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 ServerLevel 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 ServerLevel 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 ServerLevel 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 ServerLevel 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 ServerLevel currentlyTickingServerLevel; } + // SparklyPaper end } diff --git a/src/main/java/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java b/src/main/java/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java index 155bd3d6d9c7d3cac7fd04de8210301251d1e17a..f51d90f54ae693e7e9c8aa0ea14af9fdd26351f9 100644 --- a/src/main/java/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java @@ -32,7 +32,7 @@ public abstract class AbstractProjectileDispenseBehavior extends DefaultDispense CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) enumdirection.getStepX(), (double) ((float) enumdirection.getStepY() + 0.1F), (double) enumdirection.getStepZ())); - 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/BoatDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java index 6df0db8b4cdab23494ea34236949ece4989110a3..62943b954835fb30ce916c1a17fed39620a7882d 100644 --- a/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java @@ -63,7 +63,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 6c78d90e73b47b62c4052727730850d4b67a9cd2..4c42889e680415caab304f230979f352ced1872e 100644 --- a/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java @@ -85,7 +85,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 bf1c5834717758991c1520afd4b2a5c3fa68a558..c1387cd2726083b52f2ba51297072de3bb74281e 100644 --- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java @@ -222,7 +222,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); } @@ -277,7 +277,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); } @@ -333,7 +333,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); } @@ -389,7 +389,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) entityhorseabstract.getBukkitEntity()); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking world.getCraftServer().getPluginManager().callEvent(event); } @@ -463,7 +463,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); } @@ -502,7 +502,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(enumdirection.getStepX(), enumdirection.getStepY(), enumdirection.getStepZ())); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking worldserver.getCraftServer().getPluginManager().callEvent(event); } @@ -560,7 +560,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d3, d4, d5)); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking worldserver.getCraftServer().getPluginManager().callEvent(event); } @@ -639,7 +639,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); } @@ -713,7 +713,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); } @@ -760,7 +760,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); } @@ -821,7 +821,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); } @@ -850,8 +850,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(); @@ -890,7 +890,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); } @@ -947,7 +947,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); } @@ -996,7 +996,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); } @@ -1069,7 +1069,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); } diff --git a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java index 887e75c940ab5089f4e42e4553ab95adf172df46..c477e31142be8ee20f1dbba7ad1f5b7baad7437d 100644 --- a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java @@ -40,7 +40,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 6f2adf2334e35e8a617a4ced0c1af2abf32bbd8d..a5ea9df0a021ed820c0c1ccb612caebd582878e2 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 f34a9d3a255ee72e2c467376bd1f7bc2a1309443..cbb830d36c5ad753a650667f7fd178fca83d49fb 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -152,6 +152,7 @@ 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 net.minecraft.world.level.storage.loot.LootDataManager; +import org.bukkit.craftbukkit.event.CraftEventFactory; import org.slf4j.Logger; // CraftBukkit start @@ -316,6 +317,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop entitiesWithScheduledTasks = java.util.concurrent.ConcurrentHashMap.newKeySet(); // SparklyPaper - skip EntityScheduler's executeTick checks if there isn't any tasks to be run (concurrent because plugins may schedule tasks async) public 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(); @@ -1708,63 +1712,124 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper + // SparklyPaper start - parallel world ticking + java.util.ArrayDeque> tasks = new java.util.ArrayDeque<>(); + // while (iterator.hasNext()) { // SparklyPaper - commented out to cause diff when upstream changes this code + // ServerLevel worldserver = (ServerLevel) iterator.next(); + // worldserver.updateLagCompensationTick(); // Paper - lag compensation + // worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper + // net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper + // worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper + // + // this.profiler.push(() -> { + // return worldserver + " " + worldserver.dimension().location(); + // }); + // /* Drop global time updates + // if (this.tickCount % 20 == 0) { + // this.profiler.push("timeSync"); + // this.synchronizeTime(worldserver); + // this.profiler.pop(); + // } + // // CraftBukkit end */ + // + // this.profiler.push("tick"); + // + // try { + // worldserver.timings.doTick.startTiming(); // Spigot + // long i = Util.getNanos(); // SparklyPaper - track world's MSPT + // worldserver.tick(shouldKeepTicking); + // // SparklyPaper start - track world's MSPT + // long j = this.tickTimes[this.tickCount % 100] = 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 + // // Paper start + // for (final io.papermc.paper.chunk.SingleThreadChunkRegionManager regionManager : worldserver.getChunkSource().chunkMap.regionManagers) { + // regionManager.recalculateRegions(); + // } + // // Paper end + // worldserver.timings.doTick.stopTiming(); // Spigot + // } catch (Throwable throwable) { + // // Spigot Start + // CrashReport crashreport; + // try { + // crashreport = CrashReport.forThrowable(throwable, "Exception ticking world"); + // } catch (Throwable t) { + // if (throwable instanceof ThreadDeath) { throw (ThreadDeath)throwable; } // Paper + // throw new RuntimeException("Error generating crash report", t); + // } + // // Spigot End + // + // worldserver.fillReportDetails(crashreport); + // throw new ReportedException(crashreport); + // } + // + // this.profiler.pop(); + // this.profiler.pop(); + // worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions + // } + try { + while (iterator.hasNext()) { + ServerLevel worldserver = (ServerLevel) iterator.next(); + worldserver.updateLagCompensationTick(); // Paper - lag compensation + worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper + net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper + worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper + + serverLevelTickingSemaphore.acquire(); + tasks.add( + worldserver.tickExecutor.submit(() -> { + try { + io.papermc.paper.util.TickThread.ServerLevelTickThread currentThread = (io.papermc.paper.util.TickThread.ServerLevelTickThread) Thread.currentThread(); + currentThread.currentlyTickingServerLevel = worldserver; + + long i = Util.getNanos(); // SparklyPaper - track world's MSPT + worldserver.tick(shouldKeepTicking); + for (final io.papermc.paper.chunk.SingleThreadChunkRegionManager regionManager : worldserver.getChunkSource().chunkMap.regionManagers) { + regionManager.recalculateRegions(); + } + worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions - this.profiler.push(() -> { - return worldserver + " " + worldserver.dimension().location(); - }); - /* Drop global time updates - if (this.tickCount % 20 == 0) { - this.profiler.push("timeSync"); - this.synchronizeTime(worldserver); - this.profiler.pop(); - } - // CraftBukkit end */ + // SparklyPaper start - track world's MSPT + long j = Util.getNanos() - i; - this.profiler.push("tick"); + // 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 - try { - worldserver.timings.doTick.startTiming(); // Spigot - 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 - // Paper start - for (final io.papermc.paper.chunk.SingleThreadChunkRegionManager regionManager : worldserver.getChunkSource().chunkMap.regionManagers) { - regionManager.recalculateRegions(); - } - // Paper end - worldserver.timings.doTick.stopTiming(); // Spigot - } catch (Throwable throwable) { - // Spigot Start - CrashReport crashreport; - try { - crashreport = CrashReport.forThrowable(throwable, "Exception ticking world"); - } catch (Throwable t) { - if (throwable instanceof ThreadDeath) { throw (ThreadDeath)throwable; } // Paper - throw new RuntimeException("Error generating crash report", t); - } - // Spigot End + currentThread.currentlyTickingServerLevel = null; // Reset current ticking level + } catch (Throwable throwable) { + // Spigot Start + CrashReport crashreport; + try { + crashreport = CrashReport.forThrowable(throwable, "Exception ticking world"); + } catch (Throwable t) { + if (throwable instanceof ThreadDeath) { throw (ThreadDeath)throwable; } // Paper + throw new RuntimeException("Error generating crash report", t); + } + // Spigot End - worldserver.fillReportDetails(crashreport); - throw new ReportedException(crashreport); + worldserver.fillReportDetails(crashreport); + throw new ReportedException(crashreport); + } finally { + serverLevelTickingSemaphore.release(); + } + }, worldserver) + ); } - this.profiler.pop(); - this.profiler.pop(); - worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions + while (!tasks.isEmpty()) { + tasks.pop().get(); + } + } catch (java.lang.InterruptedException | java.util.concurrent.ExecutionException e) { + throw new RuntimeException(e); // Propagate exception } + // SparklyPaper end this.isIteratingOverLevels = false; // Paper this.profiler.popPush("connection"); diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java index 734fd1ced38a16649177bb941e76d9ca66dceac5..f1cc1083e6093d3813042f94c5229993d7b5df44 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java @@ -17,6 +17,7 @@ import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Optional; +import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; import javax.annotation.Nullable; import net.minecraft.DefaultUncaughtExceptionHandler; @@ -50,6 +51,7 @@ import net.minecraft.world.level.GameRules; import net.minecraft.world.level.GameType; import net.minecraft.world.level.block.entity.SkullBlockEntity; import net.minecraft.world.level.storage.LevelStorageSource; +import net.sparklypower.sparklypaper.configs.SparklyPaperConfigUtils; import org.slf4j.Logger; // CraftBukkit start @@ -226,6 +228,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/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java index 22213a52c5d546145d8b8de929af61e49d40375d..40878c28fa1e383e88c7ce2dfa142ab06582cf27 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -236,7 +236,7 @@ public class ServerChunkCache extends ChunkSource { public LevelChunk getChunkAtIfLoadedImmediately(int x, int z) { long k = ChunkPos.asLong(x, z); - if (io.papermc.paper.util.TickThread.isTickThread()) { // Paper - rewrite chunk system + if (io.papermc.paper.util.TickThread.isTickThreadFor(level, x, z)) { // Paper - rewrite chunk system // SparklyPaper - parallel world ticking return this.getChunkAtIfLoadedMainThread(x, z); } @@ -263,7 +263,16 @@ public class ServerChunkCache extends ChunkSource { @Override public ChunkAccess getChunk(int x, int z, ChunkStatus leastStatus, boolean create) { final int x1 = x; final int z1 = z; // Paper - conflict on variable change - if (!io.papermc.paper.util.TickThread.isTickThread()) { // Paper - rewrite chunk system + if (!io.papermc.paper.util.TickThread.isTickThreadFor(level, x, z)) { // Paper - rewrite chunk system // SparklyPaper - parallel world ticking + // SparklyPaper start - parallel world ticking + if (io.papermc.paper.util.TickThread.isServerLevelTickThread()) { + // Welp, we are going to crash, bye + net.minecraft.server.MinecraftServer.LOGGER.error("THE SERVER IS GOING TO CRASH! - Thread " + Thread.currentThread().getName() + " failed main thread check: Cannot query another world's (" + level.getWorld().getName() + ") chunk (" + x + ", " + z + ") in a ServerLevelTickThread - " + io.papermc.paper.util.TickThread.getTickThreadInformation(), new Throwable()); + if (io.papermc.paper.util.TickThread.HARD_THROW) + throw new IllegalStateException("Cannot query another world's (" + level.getWorld().getName() + ") chunk (" + x + ", " + z + ") in a ServerLevelTickThread"); + } + // SparklyPaper end + return (ChunkAccess) CompletableFuture.supplyAsync(() -> { return this.getChunk(x, z, leastStatus, create); }, this.mainThreadProcessor).join(); @@ -315,7 +324,7 @@ public class ServerChunkCache extends ChunkSource { @Nullable @Override public LevelChunk getChunkNow(int chunkX, int chunkZ) { - if (!io.papermc.paper.util.TickThread.isTickThread()) { // Paper - rewrite chunk system + if (!io.papermc.paper.util.TickThread.isTickThreadFor(level, chunkX, chunkZ)) { // Paper - rewrite chunk system // SparklyPaper - parallel world ticking return null; } else { return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - optimise for loaded chunks @@ -329,7 +338,7 @@ public class ServerChunkCache extends ChunkSource { } public CompletableFuture> getChunkFuture(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { - boolean flag1 = io.papermc.paper.util.TickThread.isTickThread(); // Paper - rewrite chunk system + boolean flag1 = io.papermc.paper.util.TickThread.isTickThreadFor(level, chunkX, chunkZ); // Paper - rewrite chunk system // SparklyPaper - parallel world ticking CompletableFuture completablefuture; if (flag1) { @@ -646,7 +655,7 @@ public class ServerChunkCache extends ChunkSource { if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - optimise chunk tick iteration this.level.tickChunk(chunk1, l); - if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper + // if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper } } } diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index 6999dde76bb98f9a1cb74a24fa518d705421e58e..f53b7230a49678824039bea94fc5bd1501b38bf4 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -218,6 +218,7 @@ public class ServerLevel extends Level implements WorldGenLevel { private final boolean tickTime; private final RandomSequences randomSequences; public long lastMidTickExecuteFailure; // Paper - execute chunk tasks mid tick + public java.util.concurrent.ExecutorService tickExecutor; // SparklyPaper - parallel world ticking // CraftBukkit start public final LevelStorageSource.LevelStorageAccess convertable; @@ -710,7 +711,7 @@ public class ServerLevel extends Level implements WorldGenLevel { this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile()); // CraftBukkit end this.players = Lists.newArrayList(); - this.entityTickList = new EntityTickList(); + this.entityTickList = new EntityTickList(this); this.blockTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded, this.getProfilerSupplier()); this.fluidTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded, this.getProfilerSupplier()); this.navigatingMobs = new ObjectOpenHashSet(); @@ -781,6 +782,7 @@ public class ServerLevel extends Level implements WorldGenLevel { this.chunkTaskScheduler = new io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler(this, io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.workerThreads); // Paper - rewrite chunk system this.entityLookup = new io.papermc.paper.chunk.system.entity.EntityLookup(this, new EntityCallbacks()); // Paper - rewrite chunk system + this.tickExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new net.sparklypower.sparklypaper.ServerLevelTickExecutorThreadFactory(getWorld().getName())); // SparklyPaper - parallel world ticking } // Paper start @@ -1358,7 +1360,7 @@ public class ServerLevel extends Level implements WorldGenLevel { if (fluid1.is(fluid)) { fluid1.tick(this, pos); } - MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick + // MinecraftServer.getServer().executeMidTickTasks(); // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper - exec chunk tasks during world tick } @@ -1368,7 +1370,7 @@ public class ServerLevel extends Level implements WorldGenLevel { if (iblockdata.is(block)) { iblockdata.tick(this, pos, this.random); } - MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick + // MinecraftServer.getServer().executeMidTickTasks(); // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper - exec chunk tasks during world tick } @@ -1386,7 +1388,7 @@ public class ServerLevel extends Level implements WorldGenLevel { public void tickNonPassenger(Entity entity) { // Paper start - log detailed entity tick information - io.papermc.paper.util.TickThread.ensureTickThread("Cannot tick an entity off-main"); + io.papermc.paper.util.TickThread.ensureTickThread(entity, "Cannot tick an entity off-main"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) try { if (currentlyTickingEntity.get() == null) { currentlyTickingEntity.lazySet(entity); @@ -1674,6 +1676,7 @@ public class ServerLevel extends Level implements WorldGenLevel { } private void addPlayer(ServerPlayer player) { + io.papermc.paper.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) { @@ -1687,7 +1690,7 @@ public class ServerLevel extends Level implements WorldGenLevel { // CraftBukkit start private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { - org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot + io.papermc.paper.util.TickThread.ensureTickThread(this, "Cannot add entity off-main"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) // Paper start if (entity.valid) { MinecraftServer.LOGGER.error("Attempted Double World add on " + entity, new Throwable()); @@ -2672,6 +2675,7 @@ public class ServerLevel extends Level implements WorldGenLevel { @Override public void close() throws IOException { super.close(); + tickExecutor.shutdown(); // SparklyPaper - parallel world ticking //this.entityManager.close(); // Paper - rewrite chunk system } diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java index be05a52be037042c6158100e2ce880b8ed415d53..0eda3d6699a35ffe320c7fc7b86e4573c9d4e6cd 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -17,6 +17,7 @@ import java.util.OptionalInt; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nullable; + import net.minecraft.BlockUtil; import net.minecraft.ChatFormatting; import net.minecraft.CrashReport; @@ -95,7 +96,6 @@ import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.util.Unit; import net.minecraft.world.damagesource.DamageSource; -import net.minecraft.world.damagesource.DamageSources; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.Entity; @@ -118,12 +118,7 @@ import net.minecraft.world.entity.projectile.AbstractArrow; import net.minecraft.world.entity.vehicle.AbstractMinecart; import net.minecraft.world.entity.vehicle.Boat; import net.minecraft.world.food.FoodData; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraft.world.inventory.ContainerListener; -import net.minecraft.world.inventory.ContainerSynchronizer; -import net.minecraft.world.inventory.HorseInventoryMenu; -import net.minecraft.world.inventory.ResultSlot; -import net.minecraft.world.inventory.Slot; +import net.minecraft.world.inventory.*; import net.minecraft.world.item.ComplexItem; import net.minecraft.world.item.ItemCooldowns; import net.minecraft.world.item.ItemStack; @@ -328,6 +323,7 @@ public class ServerPlayer extends Player { // Paper start - optimise chunk tick iteration public double lastEntitySpawnRadiusSquared = -1.0; // Paper end - optimise chunk tick iteration + 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); @@ -717,6 +713,7 @@ public class ServerPlayer extends Player { @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; @@ -735,6 +732,7 @@ public class ServerPlayer extends Player { containerUpdateDelay = this.level().paperConfig().tickRates.containerUpdate; } // Paper end + // SparklyPaper - parallel world ticking (debugging) if (!this.level().isClientSide && this.containerMenu != this.inventoryMenu && (this.isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - auto close while frozen this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper this.containerMenu = this.inventoryMenu; @@ -1185,6 +1183,7 @@ public class ServerPlayer extends Player { ResourceKey resourcekey = worldserver1.getTypeKey(); // CraftBukkit if (resourcekey == LevelStem.END && worldserver != null && worldserver.getTypeKey() == LevelStem.OVERWORLD) { // CraftBukkit + io.papermc.paper.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) this.isChangingDimension = true; // CraftBukkit - Moved down from above this.unRide(); this.serverLevel().removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); @@ -1197,6 +1196,10 @@ public class ServerPlayer extends Player { return this; } else { + if (worldserver != null) + io.papermc.paper.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) + else + io.papermc.paper.util.TickThread.ensureOnlyTickThread("Cannot change dimension of a player off-main, from world " + serverLevel().getWorld().getName() + " to unknown world"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) // CraftBukkit start /* WorldData worlddata = worldserver.getLevelData(); @@ -1605,6 +1608,12 @@ public class ServerPlayer extends Player { 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 // CraftBukkit end @@ -1666,6 +1675,11 @@ public class ServerPlayer extends Player { } @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 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 e98a455b6bca9d094d0da323bddd7b3f2c07bb23..7b82aae91fda6a73c2ca2e27a89a2ec34ba0fa3c 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -109,7 +109,6 @@ import org.bukkit.Location; import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.entity.CraftPlayer; -import org.bukkit.craftbukkit.util.CraftChatMessage; import org.bukkit.craftbukkit.util.CraftLocation; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerChangedWorldEvent; @@ -118,7 +117,6 @@ import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.player.PlayerRespawnEvent.RespawnReason; -import org.bukkit.event.player.PlayerSpawnChangeEvent; // CraftBukkit end public abstract class PlayerList { @@ -134,7 +132,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; @@ -155,7 +153,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 - Team name used for collideRule public PlayerList(MinecraftServer server, LayeredRegistryAccess registryManager, PlayerDataStorage saveHandler, int maxPlayers) { @@ -179,6 +177,7 @@ public abstract class PlayerList { abstract public void loadAndSaveFiles(); // Paper - moved from DedicatedPlayerList constructor public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) { + io.papermc.paper.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 GameProfile gameprofile = player.getGameProfile(); @@ -818,6 +817,8 @@ public abstract class PlayerList { } public ServerPlayer respawn(ServerPlayer entityplayer, ServerLevel worldserver, boolean flag, Location location, boolean avoidSuffocation, RespawnReason reason, org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag...respawnFlags) { + System.out.println("respawning player - current player container is " + entityplayer.containerMenu + " but their inventory is " + entityplayer.inventoryMenu); + io.papermc.paper.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, from world " + entityplayer.serverLevel().getWorld().getName() + " to world " + worldserver.getWorld().getName()); // SparklyPaper - parallel world ticking (additional concurrency issues logs) // Paper end entityplayer.stopRiding(); // CraftBukkit this.players.remove(entityplayer); @@ -842,6 +843,7 @@ public abstract class PlayerList { ServerPlayer entityplayer1 = entityplayer; org.bukkit.World fromWorld = entityplayer.getBukkitEntity().getWorld(); 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 aa6045039450c4cce8d9481aa7f56867dd15c0fa..807a11fa10b1aeb46c9e4e47b9477a5f15d86149 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -808,7 +808,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S // 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.handleNetherPortal(); } } @@ -938,11 +938,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S // This will be called every single tick the entity is in lava, so don't throw an event this.setSecondsOnFire(15, false); } - CraftEventFactory.blockDamage = (this.lastLavaContact) == null ? null : org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.lastLavaContact); + CraftEventFactory.blockDamageRT.set((this.lastLavaContact) == null ? null : org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.lastLavaContact)); // SparklyPaper - parallel world ticking if (this.hurt(this.damageSources().lava(), 4.0F)) { this.playSound(SoundEvents.GENERIC_BURN, 0.4F, 2.0F + this.random.nextFloat() * 0.4F); } - CraftEventFactory.blockDamage = null; + CraftEventFactory.blockDamageRT.set(null); // SparklyPaper - parallel world ticking // CraftBukkit end - we also don't throw an event unless the object in lava is living, to save on some event calls } @@ -3440,9 +3440,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S if (this.fireImmune()) { return; } - CraftEventFactory.entityDamage = lightning; + CraftEventFactory.entityDamageRT.set(lightning); // SparklyPaper - parallel world ticking if (!this.hurt(this.damageSources().lightningBolt(), 5.0F)) { - CraftEventFactory.entityDamage = null; + CraftEventFactory.entityDamageRT.set(null); // SparklyPaper - parallel world ticking return; } // CraftBukkit end @@ -3958,6 +3958,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S this.teleportPassengers(); this.setYHeadRot(yaw); } else { + io.papermc.paper.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) this.unRide(); Entity entity = this.getType().create(world); diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java index d8056421249c8e75e96a55ec07dce84d2bba9c5c..b7150ab57334ca4dcecb7ce5510cd19e5ca2e087 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java +++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java @@ -336,9 +336,9 @@ public class Turtle extends Animal { @Override public void thunderHit(ServerLevel world, LightningBolt lightning) { - org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = lightning; // CraftBukkit + org.bukkit.craftbukkit.event.CraftEventFactory.entityDamageRT.set(lightning); // CraftBukkit // SparklyPaper - parallel world ticking this.hurt(this.damageSources().lightningBolt(), Float.MAX_VALUE); - org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = null; // CraftBukkit + org.bukkit.craftbukkit.event.CraftEventFactory.entityDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking } @Override diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java index 45c07733f03b5c11f6d8e820f65dc950c70d9a67..60b9219778472e489192ca3259b4649a254187eb 100644 --- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java @@ -295,9 +295,9 @@ public class FallingBlockEntity extends Entity { float f2 = (float) Math.min(Mth.floor((float) i * this.fallDamagePerDistance), this.fallDamageMax); this.level().getEntities((Entity) this, this.getBoundingBox(), predicate).forEach((entity) -> { - CraftEventFactory.entityDamage = this; // CraftBukkit + CraftEventFactory.entityDamageRT.set(this); // CraftBukkit // SparklyPaper - parallel world ticking entity.hurt(damagesource2, f2); - CraftEventFactory.entityDamage = null; // CraftBukkit + CraftEventFactory.entityDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking }); boolean flag = this.blockState.is(BlockTags.ANVIL); diff --git a/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java b/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java index bbdb82b319480b103df463cce3c1b8e3dd5857ec..46093cd5d70ad6a95b2407aa5d749a3790ccd92a 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java +++ b/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java @@ -129,9 +129,9 @@ public class EvokerFangs extends Entity implements TraceableEntity { if (target.isAlive() && !target.isInvulnerable() && target != entityliving1) { if (entityliving1 == null) { - org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = this; // CraftBukkit + org.bukkit.craftbukkit.event.CraftEventFactory.entityDamageRT.set(this); // CraftBukkit // SparklyPaper - parallel world ticking target.hurt(this.damageSources().magic(), 6.0F); - org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = null; // CraftBukkit + org.bukkit.craftbukkit.event.CraftEventFactory.entityDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking } else { if (entityliving1.isAlliedTo((Entity) target)) { return; diff --git a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java index b2f08889139dc447f7071f1c81456035bf8de31e..f816f197df8f36c83d5fe5b6d677da91bac2c16f 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java +++ b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java @@ -240,9 +240,9 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { if (f > 0.0F) { if (this.attachedToEntity != null) { - CraftEventFactory.entityDamage = this; // CraftBukkit + CraftEventFactory.entityDamageRT.set(this); // CraftBukkit // SparklyPaper - parallel world ticking this.attachedToEntity.hurt(this.damageSources().fireworks(this, this.getOwner()), 5.0F + (float) (nbttaglist.size() * 2)); - CraftEventFactory.entityDamage = null; // CraftBukkit + CraftEventFactory.entityDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking } double d0 = 5.0D; @@ -269,9 +269,9 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { if (flag) { float f1 = f * (float) Math.sqrt((5.0D - (double) this.distanceTo(entityliving)) / 5.0D); - CraftEventFactory.entityDamage = this; // CraftBukkit + CraftEventFactory.entityDamageRT.set(this); // CraftBukkit // SparklyPaper - parallel world ticking entityliving.hurt(this.damageSources().fireworks(this, this.getOwner()), f1); - CraftEventFactory.entityDamage = null; // CraftBukkit + CraftEventFactory.entityDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking } } } diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java index af4da25c9b13c114179fab3254aea5323210d7da..92347924a1ccea98daee9c0fe2bb403d6b3aad22 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java +++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java @@ -85,9 +85,9 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { entityplayer.connection.teleport(teleEvent.getTo()); entity.resetFallDistance(); - CraftEventFactory.entityDamage = this; + CraftEventFactory.entityDamageRT.set(this); // SparklyPaper - parallel world ticking entity.hurt(this.damageSources().fall(), 5.0F); - CraftEventFactory.entityDamage = null; + CraftEventFactory.entityDamageRT.set(null); // SparklyPaper - parallel world ticking } // CraftBukkit end this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_TELEPORT, SoundSource.PLAYERS); diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java index 8fd82051bba33b4703e4d99fff886b63a319a5ba..ffdea352e973858931bdd4c3f7e81d179f9edfdf 100644 --- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java @@ -102,8 +102,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/ArmorItem.java b/src/main/java/net/minecraft/world/item/ArmorItem.java index 42d87800a328f71c5127ce5599ca4c71cc9bb1cd..466526dfe8f81379bccf640f2c3a70640c353540 100644 --- a/src/main/java/net/minecraft/world/item/ArmorItem.java +++ b/src/main/java/net/minecraft/world/item/ArmorItem.java @@ -69,7 +69,7 @@ public class ArmorItem extends Item implements Equipable { 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()) { // SparklyPaper - parallel world ticking world.getCraftServer().getPluginManager().callEvent(event); } diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java index de277d61b718fe07a87d75a2547bb1c7f8553aa1..d4499bc920ec8c8f59b39d0ed0ff58df5e6b089c 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java @@ -399,8 +399,8 @@ public final class ItemStack { 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/item/MinecartItem.java b/src/main/java/net/minecraft/world/item/MinecartItem.java index 3aa73cd44aa8c86b78c35bc1788e4f83018c49ed..eac9b4148d0951f7c54ce4ad8ab4d97eb5f74578 100644 --- a/src/main/java/net/minecraft/world/item/MinecartItem.java +++ b/src/main/java/net/minecraft/world/item/MinecartItem.java @@ -70,7 +70,7 @@ public class MinecartItem extends Item { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseEvent event = new BlockDispenseEvent(block2, craftItem.clone(), new org.bukkit.util.Vector(d0, d1 + d3, d2)); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking worldserver.getCraftServer().getPluginManager().callEvent(event); } diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java index 9442f58dff89ec843c321533965fbee2727d02f8..ab30f345381e32a1751adbd2344d8f3c473553ae 100644 --- a/src/main/java/net/minecraft/world/level/Explosion.java +++ b/src/main/java/net/minecraft/world/level/Explosion.java @@ -584,7 +584,7 @@ public class Explosion { continue; } - CraftEventFactory.entityDamage = this.source; + CraftEventFactory.entityDamageRT.set(this.source); // SparklyPaper - parallel world ticking entity.lastDamageCancelled = false; if (entity instanceof EnderDragon) { @@ -598,7 +598,7 @@ public class Explosion { entity.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, getSeenFraction(vec3d, entity, blockCache, blockPos))); // Paper - actually optimise explosions } - CraftEventFactory.entityDamage = null; + CraftEventFactory.entityDamageRT.set(null); // SparklyPaper - parallel world ticking if (entity.lastDamageCancelled) { // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Skip entity if damage event was cancelled continue; } diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java index 6aae2e409258607df170165c959a1cc7448c7f62..c74c8950058084a9d2dcd44b3d4941d5bdde75a9 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -15,6 +15,8 @@ import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.Supplier; import javax.annotation.Nullable; + +import io.papermc.paper.util.TickThread; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.ReportedException; @@ -830,7 +832,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline // Paper start - make sure loaded chunks get the inlined variant of this function net.minecraft.server.level.ServerChunkCache cps = ((ServerLevel)this).getChunkSource(); - if (cps.mainThread == Thread.currentThread()) { + if (TickThread.isTickThreadFor((ServerLevel) this, chunkX, chunkZ)) { // SparklyPaper - parallel world ticking, let tick threads load chunks via the main thread LevelChunk ifLoaded = cps.getChunkAtIfLoadedMainThread(chunkX, chunkZ); if (ifLoaded != null) { return ifLoaded; @@ -914,6 +916,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { @Override public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) { + io.papermc.paper.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 @@ -1310,7 +1313,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { tickingblockentity.tick(); // Paper start - execute chunk tasks during tick if ((this.tileTickPosition & 7) == 0) { - MinecraftServer.getServer().executeMidTickTasks(); + // MinecraftServer.getServer().executeMidTickTasks(); // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) } // Paper end - execute chunk tasks during tick } // SparklyPaper end @@ -1327,7 +1330,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public void guardEntityTick(Consumer tickConsumer, T entity) { try { tickConsumer.accept(entity); - MinecraftServer.getServer().executeMidTickTasks(); // Paper - execute chunk tasks mid tick + // MinecraftServer.getServer().executeMidTickTasks(); // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper - execute chunk tasks mid tick } catch (Throwable throwable) { if (throwable instanceof ThreadDeath) throw throwable; // Paper // Paper start - Prevent tile entity and entity crashes @@ -1434,6 +1437,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { @Nullable public BlockEntity getBlockEntity(BlockPos blockposition, boolean validate) { + io.papermc.paper.util.TickThread.ensureTickThreadOrAsyncThread((ServerLevel) this, "Cannot read world asynchronously"); // SparklyPaper start - parallel world ticking // Paper start - Optimize capturedTileEntities lookup net.minecraft.world.level.block.entity.BlockEntity blockEntity; if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(blockposition)) != null) { @@ -1441,10 +1445,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } // Paper end // CraftBukkit end - return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && !io.papermc.paper.util.TickThread.isTickThread() ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE)); // Paper - rewrite chunk system + return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && !io.papermc.paper.util.TickThread.isTickThread() ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE)); // Paper - rewrite chunk system // SparklyPaper - parallel world ticking } public void setBlockEntity(BlockEntity blockEntity) { + io.papermc.paper.util.TickThread.ensureTickThread((ServerLevel) this, "Cannot modify world asynchronously"); // SparklyPaper start - parallel world ticking BlockPos blockposition = blockEntity.getBlockPos(); if (!this.isOutsideBuildHeight(blockposition)) { @@ -1530,6 +1535,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { @Override public List getEntities(@Nullable Entity except, AABB box, Predicate predicate) { + io.papermc.paper.util.TickThread.ensureTickThread((ServerLevel)this, box, "Cannot getEntities asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) this.getProfiler().incrementCounter("getEntities"); List list = Lists.newArrayList(); ((ServerLevel)this).getEntityLookup().getEntities(except, box, list, predicate); // Paper - optimise this call @@ -1796,8 +1802,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } public final BlockPos.MutableBlockPos getRandomBlockPosition(int x, int y, int z, int l, BlockPos.MutableBlockPos out) { // Paper end - this.randValue = this.randValue * 3 + 1013904223; - int i1 = this.randValue >> 2; + int i1 = this.random.nextInt() >> 2; // SparklyPaper - parallel world ticking out.set(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); // Paper - change to setValues call return out; // Paper diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java index a9629a102c4fa4e5720e63fcf4590e9231426c62..c86f4eaf1d130a2eee820e4831f0b2dd23dd2ac3 100644 --- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java @@ -122,9 +122,9 @@ public class CactusBlock extends Block { @Override public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - CraftEventFactory.blockDamage = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); // CraftBukkit + CraftEventFactory.blockDamageRT.set(world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ())); // CraftBukkit // SparklyPaper - parallel world ticking entity.hurt(world.damageSources().cactus(), 1.0F); - CraftEventFactory.blockDamage = null; // CraftBukkit + CraftEventFactory.blockDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking } @Override diff --git a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java index 7302d07c6ff69608e75ac52fdb19f2ec1d105129..64890b15dcec19732c45e1f3f485ccd67cb00142 100644 --- a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java @@ -110,9 +110,9 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper if ((Boolean) state.getValue(CampfireBlock.LIT) && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity) entity)) { - org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = CraftBlock.at(world, pos); // CraftBukkit + org.bukkit.craftbukkit.event.CraftEventFactory.blockDamageRT.set(CraftBlock.at(world, pos)); // CraftBukkit // SparklyPaper - parallel world ticking entity.hurt(world.damageSources().inFire(), (float) this.fireDamage); - org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = null; // CraftBukkit + org.bukkit.craftbukkit.event.CraftEventFactory.blockDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking } super.entityInside(state, world, pos, entity); 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 52e92ffd6bf5d3d721807a0b3a8e2d301951f934..2b86f1ef01e5119ccf951c22f9b2da137d3f6735 100644 --- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java @@ -49,7 +49,8 @@ public class DispenserBlock extends BaseEntityBlock { object2objectopenhashmap.defaultReturnValue(new DefaultDispenseItemBehavior()); }); 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() { @@ -105,7 +106,7 @@ public class DispenserBlock extends BaseEntityBlock { if (idispensebehavior != DispenseItemBehavior.NOOP) { if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - BlockPreDispenseEvent is called here - 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 50d7235cf2ef036451db708c45a063d037051ae9..ff9b3d7cbf4ffcc81b4837da0c4eb0d54a52508b 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/MagmaBlock.java b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java index 10f5ffacc72a5e0116e2599ca83ee57a5b1ce0eb..9e52c7e4459318171163ffae5d1a6c3e66ed84b0 100644 --- a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java +++ b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java @@ -30,9 +30,9 @@ public class MagmaBlock extends Block { @Override public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) { if (!entity.isSteppingCarefully() && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity) entity)) { - org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); // CraftBukkit + org.bukkit.craftbukkit.event.CraftEventFactory.blockDamageRT.set(world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ())); // CraftBukkit // SparklyPaper - parallel world ticking entity.hurt(world.damageSources().hotFloor(), 1.0F); - org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = null; // CraftBukkit + org.bukkit.craftbukkit.event.CraftEventFactory.blockDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking } super.stepOn(world, pos, state, entity); 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 7368c76a01275223a7bd3df1d36e80a15f66edbb..46a266b5f0d347b095ea0d03b41c18ae25277b13 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 // Paper + SaplingBlock.treeTypeRT.set((this == Blocks.BROWN_MUSHROOM) ? TreeType.BROWN_MUSHROOM : TreeType.RED_MUSHROOM); // CraftBukkit // Paper // 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/PointedDripstoneBlock.java b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java index bd22d3fdecbc992b11073a74d854b7d1b43c3f6a..f6a76279119847af45c517fe481782a79f51761e 100644 --- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java +++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java @@ -150,9 +150,9 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate @Override public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { if (state.getValue(PointedDripstoneBlock.TIP_DIRECTION) == Direction.UP && state.getValue(PointedDripstoneBlock.THICKNESS) == DripstoneThickness.TIP) { - CraftEventFactory.blockDamage = CraftBlock.at(world, pos); // CraftBukkit + CraftEventFactory.blockDamageRT.set(CraftBlock.at(world, pos)); // CraftBukkit // SparklyPaper - parallel world ticking entity.causeFallDamage(fallDistance + 2.0F, 2.0F, world.damageSources().stalagmite()); - CraftEventFactory.blockDamage = null; // CraftBukkit + CraftEventFactory.blockDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking } else { super.fallOn(world, state, pos, entity, fallDistance); } 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 836c86104ed4f0d375330c9123af5d502efefa4d..8e88047053e18b2d1b56ae9e329659969b3dc08e 100644 --- a/src/main/java/net/minecraft/world/level/block/SaplingBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SaplingBlock.java @@ -34,7 +34,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() { @@ -72,8 +72,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/SweetBerryBushBlock.java b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java index f474ae7b3121de701f371b7d88e80086ec07d03d..3b7a14d76b0e24aa7747e3b37560db4c8a7370db 100644 --- a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java @@ -92,9 +92,9 @@ public class SweetBerryBushBlock extends BushBlock implements BonemealableBlock double d1 = Math.abs(entity.getZ() - entity.zOld); if (d0 >= 0.003000000026077032D || d1 >= 0.003000000026077032D) { - CraftEventFactory.blockDamage = CraftBlock.at(world, pos); // CraftBukkit + CraftEventFactory.blockDamageRT.set(CraftBlock.at(world, pos)); // CraftBukkit // SparklyPaper - parallel world ticking entity.hurt(world.damageSources().sweetBerryBush(), 1.0F); - CraftEventFactory.blockDamage = null; // CraftBukkit + CraftEventFactory.blockDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking } } 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 c134d089e55ea2ffb180f92aea020bd7647259c9..a7e1e05fdcb380de71b1bac5f7b5daca3dee2ac9 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 @@ -78,6 +78,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/ConduitBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java index 963a596154091b79ca139af6274aa323518ad1ad..2817225e5efc97d24e54800689cdda0f53baa616 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java @@ -235,11 +235,11 @@ public class ConduitBlockEntity extends BlockEntity { if (blockEntity.destroyTarget != null) { // CraftBukkit start - CraftEventFactory.blockDamage = CraftBlock.at(world, pos); + CraftEventFactory.blockDamageRT.set(CraftBlock.at(world, pos)); // SparklyPaper - parallel world ticking if (blockEntity.destroyTarget.hurt(world.damageSources().magic(), 4.0F)) { world.playSound((Player) null, blockEntity.destroyTarget.getX(), blockEntity.destroyTarget.getY(), blockEntity.destroyTarget.getZ(), SoundEvents.CONDUIT_ATTACK_TARGET, SoundSource.BLOCKS, 1.0F, 1.0F); } - CraftEventFactory.blockDamage = null; + CraftEventFactory.blockDamageRT.set(null); // SparklyPaper - parallel world ticking // CraftBukkit end } 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 65112ec3a6ea1c27f032477720ae74395523012b..41ed21bbb7762e52cd800846f546277c19ecd67e 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 @@ -43,9 +43,9 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi // Paper end 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 8cb822fed0cda4268f288feae1079a3dc4dc103e..1bac955a0b734689cc66b1a61e58fcde4cc3f15b 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,51 +175,53 @@ 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_REDWOOD; + treeType = TreeType.MEGA_REDWOOD; } 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 { 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 1338f91a85f5db6ce78705a0c48bec8a449a6220..00af4b8db53a55d252a43a29c4939fea111d3f13 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -419,6 +419,7 @@ public class LevelChunk extends ChunkAccess { @Nullable public BlockState setBlockState(BlockPos blockposition, BlockState iblockdata, boolean flag, boolean doPlace) { + io.papermc.paper.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 4cdfc433df67afcd455422e9baf56f167dd712ae..f52b3740bd48f8527a36d48a0454e7d601763985 100644 --- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java +++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java @@ -9,6 +9,13 @@ import net.minecraft.world.entity.Entity; public class EntityTickList { private final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet entities = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(true); // Paper - rewrite this, always keep this updated - why would we EVER tick an entity that's not ticking? + // 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 - replace with better logic, do not delay removals @@ -16,13 +23,13 @@ public class EntityTickList { } public void add(Entity entity) { - io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist addition"); // Paper + io.papermc.paper.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist addition"); // Paper // SparklyPaper - parallel world ticking (additional concurrency issues logs) this.ensureActiveIsNotIterated(); this.entities.add(entity); // Paper - replace with better logic, do not delay removals/additions } public void remove(Entity entity) { - io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist removal"); // Paper + io.papermc.paper.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist removal"); // Paper // SparklyPaper - parallel world ticking (additional concurrency issues logs) this.ensureActiveIsNotIterated(); this.entities.remove(entity); // Paper - replace with better logic, do not delay removals/additions } @@ -32,7 +39,7 @@ public class EntityTickList { } public void forEach(Consumer action) { - io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist iteration"); // Paper + io.papermc.paper.util.TickThread.ensureTickThread(serverLevel, "Asynchronous entity ticklist iteration"); // Paper // SparklyPaper - parallel world ticking (additional concurrency issues logs) // Paper start - replace with better logic, do not delay removals/additions // To ensure nothing weird happens with dimension travelling, do not iterate over new entries... // (by dfl iterator() is configured to not iterate over new entries) diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 38d842bc0fb7d9c39a3673983a643248e9563fe2..f6663c2a9984955057e5f070f31b4825d956cf60 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -434,7 +434,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { } private boolean unloadChunk0(int x, int z, boolean save) { - org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot + io.papermc.paper.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; } @@ -449,7 +449,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean regenerateChunk(int x, int z) { - org.spigotmc.AsyncCatcher.catchOp("chunk regenerate"); // Spigot + io.papermc.paper.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 // Paper start - implement regenerateChunk method final ServerLevel serverLevel = this.world; @@ -510,6 +510,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean refreshChunk(int x, int z) { + io.papermc.paper.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; @@ -545,7 +546,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 + io.papermc.paper.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 // Paper start - Optimize this method ChunkPos chunkPos = new ChunkPos(x, z); @@ -808,6 +809,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { + io.papermc.paper.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); @@ -918,11 +920,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source) { + io.papermc.paper.util.TickThread.ensureTickThread(this.world, x, z, "Cannot create explosion asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) return !this.world.explode(source == null ? null : ((CraftEntity) source).getHandle(), x, y, z, power, setFire, breakBlocks ? net.minecraft.world.level.Level.ExplosionInteraction.MOB : net.minecraft.world.level.Level.ExplosionInteraction.NONE).wasCanceled; } // Paper start @Override public boolean createExplosion(Entity source, Location loc, float power, boolean setFire, boolean breakBlocks) { + io.papermc.paper.util.TickThread.ensureTickThread(world, loc.getX(), loc.getZ(), "Cannot create explosion asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) return !world.explode(source != null ? ((org.bukkit.craftbukkit.entity.CraftEntity) source).getHandle() : null, loc.getX(), loc.getY(), loc.getZ(), power, setFire, breakBlocks ? net.minecraft.world.level.Level.ExplosionInteraction.MOB : net.minecraft.world.level.Level.ExplosionInteraction.NONE).wasCanceled; } // Paper end @@ -992,6 +996,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { + io.papermc.paper.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); @@ -1022,6 +1027,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void setBiome(int x, int y, int z, Holder bb) { BlockPos pos = new BlockPos(x, 0, z); + io.papermc.paper.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); @@ -2366,6 +2372,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { + io.papermc.paper.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())), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position)); } // Paper end @@ -2469,7 +2476,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { // Paper start public java.util.concurrent.CompletableFuture getChunkAtAsync(int x, int z, boolean gen, boolean urgent) { warnUnsafeChunk("getting a faraway chunk async", x, z); // Paper - if (Bukkit.isPrimaryThread()) { + if (io.papermc.paper.util.TickThread.isTickThreadFor(this.getHandle(), x, z)) { // SparklyPaper - parallel world ticking net.minecraft.world.level.chunk.LevelChunk immediate = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); if (immediate != null) { return java.util.concurrent.CompletableFuture.completedFuture(new CraftChunk(immediate)); diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java index e5506a7d074a9f89d41f4d5d7549a458779bef20..8481a7dfc04b9d663fec909207b744a647067d84 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) { + io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // Folia end - parallel world ticking return this.world.getBlockState(this.position); } @@ -151,6 +156,11 @@ public class CraftBlock implements Block { } private void setData(final byte data, int flag) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + io.papermc.paper.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); } @@ -192,6 +202,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) { + io.papermc.paper.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 @@ -337,18 +353,33 @@ public class CraftBlock implements Block { @Override public Biome getBiome() { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + io.papermc.paper.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) { + io.papermc.paper.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) { + io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // SparklyPaper end - parallel world ticking this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio); } @@ -396,6 +427,11 @@ public class CraftBlock implements Block { @Override public boolean isBlockFaceIndirectlyPowered(BlockFace face) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + io.papermc.paper.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); @@ -408,6 +444,11 @@ public class CraftBlock implements Block { @Override public int getBlockPower(BlockFace face) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + io.papermc.paper.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(); @@ -478,6 +519,11 @@ public class CraftBlock implements Block { @Override public boolean breakNaturally() { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // SparklyPaper end - parallel world ticking return this.breakNaturally(null); } @@ -537,6 +583,11 @@ public class CraftBlock implements Block { @Override public boolean applyBoneMeal(BlockFace face) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + io.papermc.paper.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(); @@ -548,8 +599,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; @@ -638,6 +689,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) { + io.papermc.paper.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(); @@ -679,6 +735,11 @@ public class CraftBlock implements Block { @Override public boolean canPlace(BlockData data) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + io.papermc.paper.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(); @@ -713,6 +774,11 @@ public class CraftBlock implements Block { @Override public void tick() { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // SparklyPaper end - parallel world ticking net.minecraft.world.level.block.state.BlockState blockData = this.getNMS(); net.minecraft.server.level.ServerLevel level = this.world.getMinecraftWorld(); diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java index a8290624d8c5b19506f628d049984d2e59c4423c..4b0cb97a9355c77eedf17d36c4313189b23aca73 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java @@ -18,7 +18,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()); @@ -27,8 +27,8 @@ public abstract class CraftBlockEntityState extends Craft try { // Paper - show location on failure // 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 390e1b7fd2721b99cb3ce268c6bc1bf0a38e08a3..9255e51954bd9a43afc366d8c414dd8af7571525 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java @@ -210,6 +210,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) { + io.papermc.paper.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) { + io.papermc.paper.util.TickThread.ensureTickThread(world.getHandle(), position, "Cannot modify world asynchronously"); // SparklyPaper - parallel world ticking net.minecraft.world.item.ItemStack nms = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item); // Modelled off EntityHuman#hasBlock diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java index 9271ff2a9ea05569e3c81886399aa7ab47efb05d..e5891ed14f688413dfe9ebd2b5af6d4b82f43f68 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java @@ -243,8 +243,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); @@ -252,7 +252,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 2aab68bac670dcd134d817940020214c7b0797f9..1bc112574337b262e88a8b30ed5b75d62d160db4 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -243,8 +243,8 @@ import org.bukkit.potion.PotionEffect; import org.bukkit.util.Vector; public class CraftEventFactory { - public static org.bukkit.block.Block blockDamage; // For use in EntityDamageByBlockEvent - public static Entity entityDamage; // For use in EntityDamageByEntityEvent + public static final ThreadLocal blockDamageRT = new ThreadLocal<>(); // For use in EntityDamageByBlockEvent // SparklyPaper - parallel world ticking (this is from Folia, fixes concurrency bugs / For use in EntityDamageByEntityEvent) + public static final ThreadLocal entityDamageRT = new ThreadLocal<>(); // For use in EntityDamageByEntityEvent // SparklyPaper - parallel world ticking (this is from Folia, fixes concurrency bugs / For use in EntityDamageByEntityEvent) // helper methods private static boolean canBuild(ServerLevel world, Player player, int x, int z) { @@ -917,7 +917,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 @@ -929,7 +929,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()) { @@ -1056,8 +1056,8 @@ public class CraftEventFactory { private static EntityDamageEvent handleEntityDamageEvent(Entity entity, DamageSource source, Map modifiers, Map> modifierFunctions, boolean cancelled) { if (source.is(DamageTypeTags.IS_EXPLOSION)) { DamageCause damageCause; - Entity damager = CraftEventFactory.entityDamage; - CraftEventFactory.entityDamage = null; + Entity damager = CraftEventFactory.entityDamageRT.get(); // SparklyPaper - parallel world ticking + CraftEventFactory.entityDamageRT.set(null); // SparklyPaper - parallel world ticking EntityDamageEvent event; if (damager == null) { event = new EntityDamageByBlockEvent(null, entity.getBukkitEntity(), DamageCause.BLOCK_EXPLOSION, modifiers, modifierFunctions, source.explodedBlockState); // Paper - handle block state in damage @@ -1118,13 +1118,13 @@ public class CraftEventFactory { } return event; } else if (source.is(DamageTypes.LAVA)) { - EntityDamageEvent event = (new EntityDamageByBlockEvent(CraftEventFactory.blockDamage, entity.getBukkitEntity(), DamageCause.LAVA, modifiers, modifierFunctions)); + EntityDamageEvent event = (new EntityDamageByBlockEvent(CraftEventFactory.blockDamageRT.get(), entity.getBukkitEntity(), DamageCause.LAVA, modifiers, modifierFunctions)); // SparklyPaper - parallel world ticking event.setCancelled(cancelled); - Block damager = CraftEventFactory.blockDamage; - CraftEventFactory.blockDamage = null; // SPIGOT-6639: Clear blockDamage to allow other entity damage during event call + Block damager = CraftEventFactory.blockDamageRT.get(); // SparklyPaper - parallel world ticking + CraftEventFactory.blockDamageRT.set(null); // SPIGOT-6639: Clear blockDamage to allow other entity damage during event call // SparklyPaper - parallel world ticking CraftEventFactory.callEvent(event); - CraftEventFactory.blockDamage = damager; // SPIGOT-6639: Re-set blockDamage so that other entities which are also getting damaged have the right cause + CraftEventFactory.blockDamageRT.set(damager); // SPIGOT-6639: Re-set blockDamage so that other entities which are also getting damaged have the right cause // SparklyPaper - parallel world ticking if (!event.isCancelled()) { event.getEntity().setLastDamageCause(event); @@ -1132,9 +1132,9 @@ public class CraftEventFactory { entity.lastDamageCancelled = true; // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Keep track if the event was canceled } return event; - } else if (CraftEventFactory.blockDamage != null) { + } else if (CraftEventFactory.blockDamageRT.get() != null) { // SparklyPaper - parallel world ticking DamageCause cause = null; - Block damager = CraftEventFactory.blockDamage; + Block damager = CraftEventFactory.blockDamageRT.get(); // SparklyPaper - parallel world ticking if (source.is(DamageTypes.CACTUS) || source.is(DamageTypes.SWEET_BERRY_BUSH) || source.is(DamageTypes.STALAGMITE) || source.is(DamageTypes.FALLING_STALACTITE) || source.is(DamageTypes.FALLING_ANVIL)) { cause = DamageCause.CONTACT; } else if (source.is(DamageTypes.HOT_FLOOR)) { @@ -1149,9 +1149,9 @@ public class CraftEventFactory { EntityDamageEvent event = new EntityDamageByBlockEvent(damager, entity.getBukkitEntity(), cause, modifiers, modifierFunctions); event.setCancelled(cancelled); - CraftEventFactory.blockDamage = null; // SPIGOT-6639: Clear blockDamage to allow other entity damage during event call + CraftEventFactory.blockDamageRT.set(null); // SPIGOT-6639: Clear blockDamage to allow other entity damage during event call // SparklyPaper - parallel world ticking CraftEventFactory.callEvent(event); - CraftEventFactory.blockDamage = damager; // SPIGOT-6639: Re-set blockDamage so that other entities which are also getting damaged have the right cause + CraftEventFactory.blockDamageRT.set(damager); // SPIGOT-6639: Re-set blockDamage so that other entities which are also getting damaged have the right cause // SparklyPaper - parallel world ticking if (!event.isCancelled()) { event.getEntity().setLastDamageCause(event); @@ -1159,10 +1159,10 @@ public class CraftEventFactory { entity.lastDamageCancelled = true; // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Keep track if the event was canceled } return event; - } else if (CraftEventFactory.entityDamage != null) { + } else if (CraftEventFactory.entityDamageRT.get() != null) { // SparklyPaper - parallel world ticking DamageCause cause = null; - CraftEntity damager = CraftEventFactory.entityDamage.getBukkitEntity(); - CraftEventFactory.entityDamage = null; + CraftEntity damager = CraftEventFactory.entityDamageRT.get().getBukkitEntity(); // SparklyPaper - parallel world ticking + CraftEventFactory.entityDamageRT.set(null); // SparklyPaper - parallel world ticking if (source.is(DamageTypes.FALLING_STALACTITE) || source.is(DamageTypes.FALLING_BLOCK) || source.is(DamageTypes.FALLING_ANVIL)) { cause = DamageCause.FALLING_BLOCK; } else if (damager instanceof LightningStrike) { @@ -2131,7 +2131,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(), new org.bukkit.util.Vector(to.getX(), to.getY(), to.getZ())); - 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..3d536f724ffdae462e3af39e85e4e39190696c37 --- /dev/null +++ b/src/main/kotlin/net/sparklypower/sparklypaper/ServerLevelTickExecutorThreadFactory.kt @@ -0,0 +1,21 @@ +package net.sparklypower.sparklypaper + +import io.papermc.paper.util.TickThread +import java.util.concurrent.ThreadFactory +import java.util.concurrent.atomic.AtomicInteger + +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 8b78f0e8b1de1a6a2506e686be9d71ced72352dd..ce608ca9640cdea0fe690ef61021355822284cf6 100644 --- a/src/main/kotlin/net/sparklypower/sparklypaper/configs/SparklyPaperConfigUtils.kt +++ b/src/main/kotlin/net/sparklypower/sparklypaper/configs/SparklyPaperConfigUtils.kt @@ -17,6 +17,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