From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: MrPowerGamerBR Date: Sun, 25 May 2025 21:40:06 -0300 Subject: [PATCH] Parallel World Ticking diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java index 69cdd304d255d52c9b7dc9b6a33ffdb630b79abe..8fc5691d608359ce848b8caecdd55ddd4372abfa 100644 --- a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java +++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java @@ -14,6 +14,7 @@ import java.util.concurrent.atomic.AtomicInteger; public class TickThread extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(TickThread.class); + public static final boolean HARD_THROW = !Boolean.getBoolean("sparklypaper.disableHardThrow"); // SparklyPaper - parallel world ticking - THIS SHOULD NOT BE DISABLED SINCE IT CAN CAUSE DATA CORRUPTION!!! Anyhow, for production servers, if you want to make a test run to see if the server could crash, you can test it with this disabled private static String getThreadContext() { return "thread=" + Thread.currentThread().getName(); @@ -26,6 +27,7 @@ public class TickThread extends Thread { public static void ensureTickThread(final String reason) { if (!isTickThread()) { LOGGER.error("Thread failed main thread check: " + reason + ", context=" + getThreadContext(), new Throwable()); + if (HARD_THROW) // SparklyPaper - parallel world ticking throw new IllegalStateException(reason); } } @@ -33,8 +35,9 @@ public class TickThread extends Thread { public static void ensureTickThread(final Level world, final BlockPos pos, final String reason) { if (!isTickThreadFor(world, pos)) { final String ex = "Thread failed main thread check: " + - reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos; + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + " - " + getTickThreadInformation(world.getServer()); LOGGER.error(ex, new Throwable()); + if (HARD_THROW) // SparklyPaper - parallel world ticking throw new IllegalStateException(ex); } } @@ -42,8 +45,9 @@ public class TickThread extends Thread { public static void ensureTickThread(final Level world, final BlockPos pos, final int blockRadius, final String reason) { if (!isTickThreadFor(world, pos, blockRadius)) { final String ex = "Thread failed main thread check: " + - reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + ", block_radius=" + blockRadius; + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + ", block_radius=" + blockRadius + " - " + getTickThreadInformation(world.getServer()); LOGGER.error(ex, new Throwable()); + if (HARD_THROW) // SparklyPaper - parallel world ticking throw new IllegalStateException(ex); } } @@ -51,8 +55,9 @@ public class TickThread extends Thread { public static void ensureTickThread(final Level world, final ChunkPos pos, final String reason) { if (!isTickThreadFor(world, pos)) { final String ex = "Thread failed main thread check: " + - reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + pos; + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + pos + " - " + getTickThreadInformation(world.getServer()); LOGGER.error(ex, new Throwable()); + if (HARD_THROW) // SparklyPaper - parallel world ticking throw new IllegalStateException(ex); } } @@ -60,8 +65,9 @@ public class TickThread extends Thread { public static void ensureTickThread(final Level world, final int chunkX, final int chunkZ, final String reason) { if (!isTickThreadFor(world, chunkX, chunkZ)) { final String ex = "Thread failed main thread check: " + - reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + new ChunkPos(chunkX, chunkZ); + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + new ChunkPos(chunkX, chunkZ) + " - " + getTickThreadInformation(world.getServer()); LOGGER.error(ex, new Throwable()); + if (HARD_THROW) // SparklyPaper - parallel world ticking throw new IllegalStateException(ex); } } @@ -69,8 +75,9 @@ public class TickThread extends Thread { public static void ensureTickThread(final Entity entity, final String reason) { if (!isTickThreadFor(entity)) { final String ex = "Thread failed main thread check: " + - reason + ", context=" + getThreadContext() + ", entity=" + EntityUtil.dumpEntity(entity); + reason + ", context=" + getThreadContext() + ", entity=" + EntityUtil.dumpEntity(entity) + " - " + getTickThreadInformation(entity.getServer()); LOGGER.error(ex, new Throwable()); + if (HARD_THROW) // SparklyPaper - parallel world ticking throw new IllegalStateException(ex); } } @@ -78,8 +85,9 @@ public class TickThread extends Thread { public static void ensureTickThread(final Level world, final AABB aabb, final String reason) { if (!isTickThreadFor(world, aabb)) { final String ex = "Thread failed main thread check: " + - reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb; + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb + " - " + getTickThreadInformation(world.getServer()); LOGGER.error(ex, new Throwable()); + if (HARD_THROW) // SparklyPaper - parallel world ticking throw new IllegalStateException(ex); } } @@ -87,12 +95,75 @@ public class TickThread extends Thread { public static void ensureTickThread(final Level world, final double blockX, final double blockZ, final String reason) { if (!isTickThreadFor(world, blockX, blockZ)) { final String ex = "Thread failed main thread check: " + - reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + new Vec3(blockX, 0.0, blockZ); + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + new Vec3(blockX, 0.0, blockZ) + " - " + getTickThreadInformation(world.getServer()); LOGGER.error(ex, new Throwable()); + if (HARD_THROW) // SparklyPaper - parallel world ticking throw new IllegalStateException(ex); } } + // SparklyPaper - parallel world ticking + // This is an additional method to check if the tick thread is bound to a specific world because, by default, Paper's isTickThread methods do not provide this information + // Because we only tick worlds in parallel (instead of regions), we can use this for our checks + public static void ensureTickThread(final net.minecraft.server.level.ServerLevel world, final String reason) { + if (!isTickThreadFor(world)) { + LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " - " + getTickThreadInformation(world.getServer()), new Throwable()); + if (HARD_THROW) + throw new IllegalStateException(reason); + } + } + + // SparklyPaper - parallel world ticking + // This is an additional method to check if it is a tick thread but ONLY a tick thread + public static void ensureOnlyTickThread(final String reason) { + boolean isTickThread = isTickThread(); + boolean isServerLevelTickThread = isServerLevelTickThread(); + if (!isTickThread || isServerLevelTickThread) { + LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread ONLY tick thread check: " + reason, new Throwable()); + if (HARD_THROW) + throw new IllegalStateException(reason); + } + } + + // SparklyPaper - parallel world ticking + // This is an additional method to check if the tick thread is bound to a specific world or if it is an async thread. + public static void ensureTickThreadOrAsyncThread(final net.minecraft.server.level.ServerLevel world, final String reason) { + boolean isValidTickThread = isTickThreadFor(world); + boolean isAsyncThread = !isTickThread(); + boolean isValid = isAsyncThread || isValidTickThread; + if (!isValid) { + LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread or async thread check: " + reason + " @ world " + world.getWorld().getName() + " - " + getTickThreadInformation(world.getServer()), new Throwable()); + if (HARD_THROW) + throw new IllegalStateException(reason); + } + } + + public static String getTickThreadInformation(net.minecraft.server.MinecraftServer minecraftServer) { + StringBuilder sb = new StringBuilder(); + Thread currentThread = Thread.currentThread(); + sb.append("Is tick thread? "); + sb.append(currentThread instanceof TickThread); + sb.append("; Is server level tick thread? "); + sb.append(currentThread instanceof ServerLevelTickThread); + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + sb.append("; Currently ticking level: "); + if (serverLevelTickThread.currentlyTickingServerLevel != null) { + sb.append(serverLevelTickThread.currentlyTickingServerLevel.getWorld().getName()); + } else { + sb.append("null"); + } + } + sb.append("; Is iterating over levels? "); + sb.append(minecraftServer.isIteratingOverLevels); + sb.append("; Are we going to hard throw? "); + sb.append(HARD_THROW); + return sb.toString(); + } + + public static boolean isServerLevelTickThread() { + return Thread.currentThread() instanceof ServerLevelTickThread; + } + public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */ private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); @@ -127,7 +198,11 @@ public class TickThread extends Thread { } public static boolean isTickThreadFor(final Level world, final BlockPos pos) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Level world, final BlockPos pos, final int blockRadius) { @@ -135,38 +210,103 @@ public class TickThread extends Thread { } public static boolean isTickThreadFor(final Level world, final ChunkPos pos) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Level world, final Vec3 pos) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Level world, final AABB aabb) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Level world, final double blockX, final double blockZ) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Level world, final Vec3 position, final Vec3 deltaMovement, final int buffer) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Level world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ, final int radius) { - return isTickThread(); + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; + } + + // SparklyPaper - parallel world ticking + // This is an additional method to check if the tick thread is bound to a specific world because, by default, Paper's isTickThread methods do not provide this information + // Because we only tick worlds in parallel (instead of regions), we can use this for our checks + public static boolean isTickThreadFor(final Level world) { + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return currentThread instanceof TickThread; } public static boolean isTickThreadFor(final Entity entity) { - return isTickThread(); + if (entity == null) { + return true; + } + + Thread currentThread = Thread.currentThread(); + + if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == entity.level(); + } else return currentThread instanceof TickThread; + } + + // SparklyPaper start - parallel world ticking + public static class ServerLevelTickThread extends TickThread { + public ServerLevelTickThread(String name) { + super(name); + } + + public ServerLevelTickThread(Runnable run, String name) { + super(run, name); + } + + public net.minecraft.server.level.ServerLevel currentlyTickingServerLevel; } + // SparklyPaper end } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 7990382df41824b7a4072fd4a44d02a229a4d9b6..343267292db75188cae483d72bc8faf03741a5b6 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -474,7 +474,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { } private boolean unloadChunk0(int x, int z, boolean save) { - org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot unload chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) if (!this.isChunkLoaded(x, z)) { return true; } @@ -491,6 +491,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean refreshChunk(int x, int z) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot refresh chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); if (playerChunk == null) return false; @@ -541,6 +542,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean loadChunk(int x, int z, boolean generate) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), x, z, "May not sync load chunks asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot warnUnsafeChunk("loading a faraway chunk", x, z); // Paper ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper @@ -769,6 +771,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, loc.getX(), loc.getZ(), "Cannot generate tree asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) this.world.captureTreeGeneration = true; this.world.captureBlockStates = true; boolean grownTree = this.generateTree(loc, type); @@ -884,6 +887,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { } public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source, Consumer configurator) { // Paper end - expand explosion API + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot create explosion asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) net.minecraft.world.level.Level.ExplosionInteraction explosionType; if (!breakBlocks) { explosionType = net.minecraft.world.level.Level.ExplosionInteraction.NONE; // Don't break blocks @@ -975,6 +979,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x >> 4, z >> 4, "Cannot retrieve chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper // Transient load for this tick return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z); @@ -1005,6 +1010,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void setBiome(int x, int y, int z, Holder bb) { BlockPos pos = new BlockPos(x, 0, z); + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, pos, "Cannot retrieve chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) if (this.world.hasChunkAt(pos)) { net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkAt(pos); @@ -2313,6 +2319,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, position.getX(), position.getZ(), "Cannot send game event asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs) getHandle().gameEvent(sourceEntity != null ? ((CraftEntity) sourceEntity).getHandle(): null, net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.get(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(gameEvent.getKey())).orElseThrow(), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position)); } // Paper end diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java index a4d5c65edc1db59f3486ce5d3757cc306211a54b..3609e71f382f80017acb91c5e286008d1684d224 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java @@ -155,6 +155,11 @@ public class CraftBlock implements Block { } private void setData(final byte data, int flags) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // SparklyPaper end - parallel world ticking this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flags); } @@ -196,6 +201,11 @@ public class CraftBlock implements Block { } public static boolean setBlockState(LevelAccessor world, BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState, boolean applyPhysics) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, pos, "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 block entity cleanup if (oldState.hasBlockEntity() && newState.getBlock() != oldState.getBlock()) { // SPIGOT-3725 remove old block entity if block changes // SPIGOT-4612: faster - just clear tile @@ -344,18 +354,33 @@ public class CraftBlock implements Block { @Override public Biome getBiome() { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // SparklyPaper end - parallel world ticking return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); } // Paper start @Override public Biome getComputedBiome() { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // SparklyPaper end - parallel world ticking return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ()); } // Paper end @Override public void setBiome(Biome bio) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // SparklyPaper end - parallel world ticking this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio); } @@ -376,6 +401,11 @@ public class CraftBlock implements Block { @Override public boolean isBlockIndirectlyPowered() { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // SparklyPaper end - parallel world ticking return this.world.getMinecraftWorld().hasNeighborSignal(this.position); } @@ -415,6 +445,11 @@ public class CraftBlock implements Block { @Override public int getBlockPower(BlockFace face) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // SparklyPaper end - parallel world ticking int power = 0; net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); int x = this.getX(); @@ -499,6 +534,11 @@ public class CraftBlock implements Block { @Override public boolean breakNaturally(ItemStack item, boolean triggerEffect, boolean dropExperience) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // SparklyPaper end - parallel world ticking // Paper end // Order matters here, need to drop before setting to air so skulls can get their data net.minecraft.world.level.block.state.BlockState state = this.getNMS(); @@ -542,6 +582,11 @@ public class CraftBlock implements Block { @Override public boolean applyBoneMeal(BlockFace face) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // SparklyPaper end - parallel world ticking Direction direction = CraftBlock.blockFaceToNotch(face); BlockFertilizeEvent event = null; ServerLevel world = this.getCraftWorld().getHandle(); @@ -553,8 +598,8 @@ public class CraftBlock implements Block { world.captureTreeGeneration = false; if (!world.capturedBlockStates.isEmpty()) { - 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 states = new ArrayList<>(world.capturedBlockStates.values()); world.capturedBlockStates.clear(); StructureGrowEvent structureEvent = null; @@ -593,6 +638,11 @@ public class CraftBlock implements Block { @Override public Collection getDrops(ItemStack item, Entity entity) { net.minecraft.world.level.block.state.BlockState state = this.getNMS(); + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // SparklyPaper end - parallel world ticking net.minecraft.world.item.ItemStack nms = CraftItemStack.asNMSCopy(item); // Modelled off Player#hasCorrectToolForDrops @@ -644,6 +694,11 @@ public class CraftBlock implements Block { @Override public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // SparklyPaper end - parallel world ticking Preconditions.checkArgument(start != null, "Location start cannot be null"); Preconditions.checkArgument(this.getWorld().equals(start.getWorld()), "Location start cannot be a different world"); start.checkFinite(); @@ -685,6 +740,11 @@ public class CraftBlock implements Block { @Override public boolean canPlace(BlockData data) { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // SparklyPaper end - parallel world ticking Preconditions.checkArgument(data != null, "BlockData cannot be null"); net.minecraft.world.level.block.state.BlockState iblockdata = ((CraftBlockData) data).getState(); net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); @@ -724,6 +784,11 @@ public class CraftBlock implements Block { @Override public void tick() { + // SparklyPaper start - parallel world ticking + if (world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // SparklyPaper end - parallel world ticking final ServerLevel level = this.world.getMinecraftWorld(); this.getNMS().tick(level, this.position, level.random); } diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java index 7232b560aac9fbe48318f7af1c875583e06723ee..731cf7ef140c454466d8961e74ad2f43c0f9242d 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java @@ -32,7 +32,7 @@ public abstract class CraftBlockEntityState extends Craft private final T blockEntity; 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 blockEntity) { super(world, blockEntity.getBlockPos(), blockEntity.getBlockState()); @@ -41,8 +41,8 @@ public abstract class CraftBlockEntityState extends Craft try { // Paper - Show blockstate location if we failed to read it // Paper start - this.snapshotDisabled = DISABLE_SNAPSHOT; - if (DISABLE_SNAPSHOT) { + this.snapshotDisabled = DISABLE_SNAPSHOT.get(); // SparklyPaper - parallel world ticking + if (DISABLE_SNAPSHOT.get()) { // SparklyPaper - parallel world ticking this.snapshot = this.blockEntity; } else { this.snapshot = this.createSnapshot(blockEntity); diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java index 196835bdf95ba0e149b2977e9ef41698971f501f..641adf9666fef4d15bc9b585aabfe687a83fbe86 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java @@ -218,6 +218,12 @@ public class CraftBlockState implements BlockState { LevelAccessor access = this.getWorldHandle(); CraftBlock block = this.getBlock(); + // SparklyPaper start - parallel world ticking + if (access instanceof net.minecraft.server.level.ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // SparklyPaper end - parallel world ticking + if (block.getType() != this.getType()) { if (!force) { return false; @@ -365,6 +371,7 @@ public class CraftBlockState implements BlockState { @Override public java.util.Collection getDrops(org.bukkit.inventory.ItemStack item, org.bukkit.entity.Entity entity) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(world.getHandle(), position, "Cannot modify world asynchronously"); // SparklyPaper - parallel world ticking this.requirePlaced(); net.minecraft.world.item.ItemStack nms = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item); diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java index cc5028cebcd0504635734907c73ee5c9d91f0e71..be3840a298fa778937803341018868351cdb535f 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java @@ -195,14 +195,14 @@ public final class CraftBlockStates { BlockPos pos = craftBlock.getPosition(); net.minecraft.world.level.block.state.BlockState state = craftBlock.getNMS(); BlockEntity blockEntity = craftBlock.getHandle().getBlockEntity(pos); - 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 { CraftBlockState blockState = CraftBlockStates.getBlockState(world, pos, state, blockEntity); blockState.setWorldHandle(craftBlock.getHandle()); // Inject the block's generator access return blockState; } finally { - CraftBlockEntityState.DISABLE_SNAPSHOT = prev; + CraftBlockEntityState.DISABLE_SNAPSHOT.set(prev); // SparklyPaper - parallel world ticking } } diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index ffcc282dec5c3874f1170b42f1752a0f8421fe35..d24656e96f4628f7d02f0e0026e2ef7258765b35 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -809,7 +809,7 @@ public class CraftEventFactory { return false; } - public static BlockPos sourceBlockOverride = null; // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPos up to five methods deep. + public static final ThreadLocal sourceBlockOverrideRT = new ThreadLocal<>(); // 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 state, int flags) { return handleBlockSpreadEvent(world, source, target, state, flags, false); @@ -825,7 +825,7 @@ public class CraftEventFactory { CraftBlockState snapshot = CraftBlockStates.getBlockState(world, target); snapshot.setData(state); - BlockSpreadEvent event = new BlockSpreadEvent(snapshot.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverride != null ? CraftEventFactory.sourceBlockOverride : source), snapshot); + BlockSpreadEvent event = new BlockSpreadEvent(snapshot.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverrideRT.get() != null ? CraftEventFactory.sourceBlockOverrideRT.get() : source), snapshot); if (event.callEvent()) { boolean result = snapshot.place(flags); return !checkSetResult || result;