From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> Date: Wed, 29 Jan 2025 01:41:25 +0300 Subject: [PATCH] SparklyPaper: Parallel world ticking Original project: https://github.com/SparklyPower/SparklyPaper diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java index 69cdd304d255d52c9b7dc9b6a33ffdb630b79abe..a4adaba06c1bc4bb8029ccb128fde90ec2564525 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 = !org.bxteam.divinemc.config.DivineConfig.AsyncCategory.disableHardThrow; // DivineMC - Parallel world ticking private static String getThreadContext() { return "thread=" + Thread.currentThread().getName(); @@ -26,14 +27,14 @@ public class TickThread extends Thread { public static void ensureTickThread(final String reason) { if (!isTickThread()) { LOGGER.error("Thread failed main thread check: " + reason + ", context=" + getThreadContext(), new Throwable()); - throw new IllegalStateException(reason); + if (HARD_THROW) throw new IllegalStateException(reason); // DivineMC - Parallel world ticking } } public static void ensureTickThread(final Level world, final BlockPos pos, final String reason) { if (!isTickThreadFor(world, pos)) { final String ex = "Thread failed main thread check: " + - reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos; + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + " - " + getTickThreadInformation(world.getServer()); // DivineMC - Parallel world ticking LOGGER.error(ex, new Throwable()); throw new IllegalStateException(ex); } @@ -42,7 +43,7 @@ public class TickThread extends Thread { public static void ensureTickThread(final Level world, final BlockPos pos, final int blockRadius, final String reason) { if (!isTickThreadFor(world, pos, blockRadius)) { final String ex = "Thread failed main thread check: " + - reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + ", block_radius=" + blockRadius; + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + ", block_radius=" + blockRadius + " - " + getTickThreadInformation(world.getServer()); // DivineMC - Parallel world ticking LOGGER.error(ex, new Throwable()); throw new IllegalStateException(ex); } @@ -60,7 +61,7 @@ public class TickThread extends Thread { public static void ensureTickThread(final Level world, final int chunkX, final int chunkZ, final String reason) { if (!isTickThreadFor(world, chunkX, chunkZ)) { final String ex = "Thread failed main thread check: " + - reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + new ChunkPos(chunkX, chunkZ); + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + new ChunkPos(chunkX, chunkZ) + " - " + getTickThreadInformation(world.getServer()); // DivineMC - Parallel world ticking LOGGER.error(ex, new Throwable()); throw new IllegalStateException(ex); } @@ -69,7 +70,7 @@ public class TickThread extends Thread { public static void ensureTickThread(final Entity entity, final String reason) { if (!isTickThreadFor(entity)) { final String ex = "Thread failed main thread check: " + - reason + ", context=" + getThreadContext() + ", entity=" + EntityUtil.dumpEntity(entity); + reason + ", context=" + getThreadContext() + ", entity=" + EntityUtil.dumpEntity(entity) + " - " + getTickThreadInformation(entity.getServer()); // DivineMC - Parallel world ticking LOGGER.error(ex, new Throwable()); throw new IllegalStateException(ex); } @@ -78,7 +79,7 @@ public class TickThread extends Thread { public static void ensureTickThread(final Level world, final AABB aabb, final String reason) { if (!isTickThreadFor(world, aabb)) { final String ex = "Thread failed main thread check: " + - reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb; + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb + " - " + getTickThreadInformation(world.getServer()); // DivineMC - Parallel world ticking LOGGER.error(ex, new Throwable()); throw new IllegalStateException(ex); } @@ -87,12 +88,67 @@ 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()); // DivineMC - Parallel world ticking LOGGER.error(ex, new Throwable()); throw new IllegalStateException(ex); } } + // DivineMC start - Parallel world ticking + 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); + } + } + + 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); + } + } + + 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; + } + // DivineMC end - Parallel world ticking + public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */ private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); @@ -126,47 +182,71 @@ public class TickThread extends Thread { return false; } + // DivineMC start - Parallel world ticking public static boolean isTickThreadFor(final Level world, final BlockPos pos) { - return isTickThread(); + return isTickThreadFor(world); } public static boolean isTickThreadFor(final Level world, final BlockPos pos, final int blockRadius) { - return isTickThread(); + return isTickThreadFor(world); } public static boolean isTickThreadFor(final Level world, final ChunkPos pos) { - return isTickThread(); + return isTickThreadFor(world); } public static boolean isTickThreadFor(final Level world, final Vec3 pos) { - return isTickThread(); + return isTickThreadFor(world); } public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ) { - return isTickThread(); + return isTickThreadFor(world); } public static boolean isTickThreadFor(final Level world, final AABB aabb) { - return isTickThread(); + return isTickThreadFor(world); } public static boolean isTickThreadFor(final Level world, final double blockX, final double blockZ) { - return isTickThread(); + return isTickThreadFor(world); } public static boolean isTickThreadFor(final Level world, final Vec3 position, final Vec3 deltaMovement, final int buffer) { - return isTickThread(); + return isTickThreadFor(world); } public static boolean isTickThreadFor(final Level world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) { - return isTickThread(); + return isTickThreadFor(world); } public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ, final int radius) { - return isTickThread(); + return isTickThreadFor(world); + } + + public static boolean isTickThreadFor(final Level world) { + if (Thread.currentThread() instanceof ServerLevelTickThread serverLevelTickThread) { + return serverLevelTickThread.currentlyTickingServerLevel == world; + } else return isTickThread(); } public static boolean isTickThreadFor(final Entity entity) { - return isTickThread(); + if (entity == null) { + return true; + } + + return isTickThreadFor(entity.level()); + } + + 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; } + // DivineMC end - Parallel world ticking } diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java index ab499a7eaccdc1578ec64f90f54f79b0da3c0e96..6bcb8069de18e8a0f4ee9d5c71b6bdd1afe640dd 100644 --- a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java @@ -28,6 +28,7 @@ import java.util.logging.Level; class PaperEventManager { private final Server server; + private final org.purpurmc.purpur.util.MinecraftInternalPlugin minecraftInternalPlugin = new org.purpurmc.purpur.util.MinecraftInternalPlugin(); // DivineMC - Parallel world ticking public PaperEventManager(Server server) { this.server = server; @@ -40,6 +41,12 @@ class PaperEventManager { if (listeners.length == 0) return; // DivineMC end - Skip event if no listeners if (event.isAsynchronous() && this.server.isPrimaryThread()) { + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && org.bxteam.divinemc.config.DivineConfig.AsyncCategory.pwtCompatabilityMode) { + org.bukkit.Bukkit.getAsyncScheduler().runNow(minecraftInternalPlugin, task -> event.callEvent()); + return; + } + // DivineMC end - Parallel world ticking throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously."); } else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) { // DivineMC start - Multithreaded Tracker diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index c1d099a3f8feccf71cad7f617c3f739120b13992..6bac46bfbb30fbc9bf2b174dd4550b49ed07acfc 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -462,7 +462,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean unloadChunkRequest(int x, int z) { - org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot unload chunk asynchronously"); + } else { + org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot + } + // DivineMC end - Parallel world ticking if (this.isChunkLoaded(x, z)) { this.world.getChunkSource().removeTicketWithRadius(TicketType.PLUGIN, new ChunkPos(x, z), 1); } @@ -488,6 +494,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean refreshChunk(int x, int z) { + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot refresh chunk asynchronously"); // DivineMC - Parallel world ticking ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); if (playerChunk == null) return false; @@ -538,7 +545,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean loadChunk(int x, int z, boolean generate) { - org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), x, z, "May not sync load chunks asynchronously"); + } else { + org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot + } + // DivineMC end - Parallel world ticking 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 @@ -751,6 +764,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, loc.getX(), loc.getZ(), "Cannot generate tree asynchronously"); // DivineMC - Parallel world ticking this.world.captureTreeGeneration = true; this.world.captureBlockStates = true; boolean grownTree = this.generateTree(loc, type); @@ -852,6 +866,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { private boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source, Consumer configurator) { // Paper end - expand explosion API + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot create explosion asynchronously"); // DivineMC - Parallel world ticking net.minecraft.world.level.Level.ExplosionInteraction explosionType; if (!breakBlocks) { explosionType = net.minecraft.world.level.Level.ExplosionInteraction.NONE; // Don't break blocks @@ -908,6 +923,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x >> 4, z >> 4, "Cannot retrieve chunk asynchronously"); // DivineMC - Parallel world ticking 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); @@ -923,6 +939,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); + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, pos, "Cannot retrieve chunk asynchronously"); // DivineMC - Parallel world ticking if (this.world.hasChunkAt(pos)) { net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkAt(pos); @@ -1902,6 +1919,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, position.getX(), position.getZ(), "Cannot send game event asynchronously"); // DivineMC - Parallel world ticking 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 52b3362259ed1ba2ce5379230b2c3b06b0ff6249..1d8c9f94198b558a6b4cdc5d4981b6b4e946903b 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() { + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // DivineMC end - Parallel world ticking return this.world.getBlockState(this.position); } @@ -155,6 +160,11 @@ public class CraftBlock implements Block { } private void setData(final byte data, int flags) { + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // DivineMC end - Parallel world ticking this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flags); } @@ -196,6 +206,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) { + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, pos, "Cannot modify world asynchronously"); + } + // DivineMC 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 +359,33 @@ public class CraftBlock implements Block { @Override public Biome getBiome() { + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // DivineMC end - Parallel world ticking return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ()); } // Paper start @Override public Biome getComputedBiome() { + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // DivineMC end - Parallel world ticking return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ()); } // Paper end @Override public void setBiome(Biome bio) { + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // DivineMC end - Parallel world ticking this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio); } @@ -403,6 +433,11 @@ public class CraftBlock implements Block { @Override public boolean isBlockFaceIndirectlyPowered(BlockFace face) { + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // DivineMC end - Parallel world ticking int power = this.world.getMinecraftWorld().getSignal(this.position, CraftBlock.blockFaceToNotch(face)); Block relative = this.getRelative(face); @@ -415,6 +450,11 @@ public class CraftBlock implements Block { @Override public int getBlockPower(BlockFace face) { + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // DivineMC end - Parallel world ticking int power = 0; net.minecraft.world.level.Level world = this.world.getMinecraftWorld(); int x = this.getX(); @@ -483,6 +523,11 @@ public class CraftBlock implements Block { @Override public boolean breakNaturally() { + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // DivineMC end - Parallel world ticking return this.breakNaturally(null); } @@ -548,6 +593,11 @@ public class CraftBlock implements Block { @Override public boolean applyBoneMeal(BlockFace face) { + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // DivineMC end - Parallel world ticking Direction direction = CraftBlock.blockFaceToNotch(face); BlockFertilizeEvent event = null; ServerLevel world = this.getCraftWorld().getHandle(); @@ -559,8 +609,10 @@ public class CraftBlock implements Block { world.captureTreeGeneration = false; if (!world.capturedBlockStates.isEmpty()) { - TreeType treeType = SaplingBlock.treeType; - SaplingBlock.treeType = null; + // DivineMC start - Parallel world ticking + TreeType treeType = SaplingBlock.getTreeTypeTL(); + SaplingBlock.setTreeTypeTL(null); + // DivineMC end - Parallel world ticking List states = new ArrayList<>(world.capturedBlockStates.values()); world.capturedBlockStates.clear(); StructureGrowEvent structureEvent = null; @@ -650,6 +702,11 @@ public class CraftBlock implements Block { @Override public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) { + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // DivineMC 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(); @@ -691,6 +748,11 @@ public class CraftBlock implements Block { @Override public boolean canPlace(BlockData data) { + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously"); + } + // DivineMC 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(); @@ -730,6 +792,11 @@ public class CraftBlock implements Block { @Override public void tick() { + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && world instanceof ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // DivineMC 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 5d4faad9df4824cfd61abfd4df011c006f114424..361ddcfaaa47f27135fd4629446b6560b60badeb 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java @@ -33,6 +33,25 @@ public abstract class CraftBlockEntityState extends Craft private final T snapshot; public boolean snapshotDisabled; // Paper public static boolean DISABLE_SNAPSHOT = false; // Paper + // DivineMC start - Parallel world ticking + public static ThreadLocal DISABLE_SNAPSHOT_TL = ThreadLocal.withInitial(() -> Boolean.FALSE); + + public static boolean getDisableSnapshotTL() { + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && DISABLE_SNAPSHOT_TL.get()) return true; + + synchronized (CraftBlockEntityState.class) { + return DISABLE_SNAPSHOT; + } + } + + public static void setDisableSnapshotTL(boolean value) { + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) DISABLE_SNAPSHOT_TL.set(value); + + synchronized (CraftBlockEntityState.class) { + DISABLE_SNAPSHOT = value; + } + } + // DivineMC end - Parallel world ticking public CraftBlockEntityState(World world, T blockEntity) { super(world, blockEntity.getBlockPos(), blockEntity.getBlockState()); @@ -41,8 +60,10 @@ 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) { + // DivineMC start - Parallel world ticking + this.snapshotDisabled = getDisableSnapshotTL(); + if (snapshotDisabled) { + // DivineMC end - 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..b35dbe2b6e75ec89483aef093474c6757983a73f 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(); + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && access instanceof net.minecraft.server.level.ServerLevel serverWorld) { + ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); + } + // DivineMC 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) { + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(world.getHandle(), position, "Cannot modify world asynchronously"); // DivineMC - 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 03418a9d7cca79a6fa682b64f9d901a4af3e1d58..f0af6319089b477c5d63aec521aee4cc69b52f23 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java @@ -196,14 +196,16 @@ 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; + // DivineMC start - Parallel world ticking + boolean prev = CraftBlockEntityState.getDisableSnapshotTL(); + CraftBlockEntityState.setDisableSnapshotTL(!useSnapshot); + // DivineMC end - 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.setDisableSnapshotTL(prev); // DivineMC - Parallel world ticking } } diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java index e4e2e42d0ca25df7fe9f2dd4275610e45fcb2c84..93bf7beab3b00973a19e51d48a9dfb26dd0a254c 100644 --- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java @@ -19,11 +19,28 @@ class CraftAsyncTask extends CraftTask { @Override public boolean isSync() { - return false; + return org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && org.bxteam.divinemc.config.DivineConfig.AsyncCategory.pwtCompatabilityMode; // DivineMC - Parallel world ticking } @Override public void run() { + // DivineMC start - Parallel world ticking + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && org.bxteam.divinemc.config.DivineConfig.AsyncCategory.pwtCompatabilityMode) { + try { + super.run(); + } catch (final Throwable t) { + this.getOwner().getLogger().log( + Level.WARNING, + String.format( + "Plugin %s generated an exception while executing task %s (sync mode)", + this.getOwner().getDescription().getFullName(), + this.getTaskId()), + t); + } + return; + } + // DivineMC end - Parallel world ticking + final Thread thread = Thread.currentThread(); // Paper start - name threads according to running plugin final String nameBefore = thread.getName(); diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java index 0e5b10153821fda6056791e1c216d05a9ac8e5bc..85699885e6be932758978eec5d2b8ff22944c7c3 100644 --- a/src/main/java/org/spigotmc/WatchdogThread.java +++ b/src/main/java/org/spigotmc/WatchdogThread.java @@ -112,6 +112,23 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre WatchdogThread.dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(MinecraftServer.getServer().serverThread.threadId(), Integer.MAX_VALUE), logger); logger.log(Level.SEVERE, "------------------------------"); + // DivineMC start - Dump Parallel World Ticking thread + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) { + logger.log(Level.SEVERE, "Parallel world ticking thread dump"); + for (Thread thread : org.apache.commons.lang3.ThreadUtils.getAllThreads()) { + if (MinecraftServer.getServer().serverThread == thread || thread instanceof WatchdogThread) { + continue; + } + if (thread instanceof ca.spottedleaf.moonrise.common.util.TickThread tickThread) { + if (tickThread instanceof ServerLevelTickThread tickThread1) { + WatchdogThread.dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(tickThread1.threadId(), Integer.MAX_VALUE), logger); + } + } + } + logger.log(Level.SEVERE, "------------------------------"); + } + // DivineMC end - Dump Parallel World Ticking thread + // Paper start - Only print full dump on long timeouts if (isLongTimeout) { logger.log(Level.SEVERE, "Entire Thread Dump:");