mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2026-01-04 15:41:40 +00:00
* Unify comment format * More configurable * Remove one extra execute mid-tick task call in level tick when PWT is disabled This may cause extremely rare, weird, strange, magic, mysterious issues with plugins, or potentially more. One example is that it may cause boss mob duplication issue when `ONE MOB ONLY` was enabled in plugin SupremeBosses
1767 lines
106 KiB
Diff
1767 lines
106 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: MrPowerGamerBR <git@mrpowergamerbr.com>
|
|
Date: Sun, 25 May 2025 21:39:32 -0300
|
|
Subject: [PATCH] SparklyPaper: Parallel world ticking
|
|
|
|
Original project: https://github.com/SparklyPower/SparklyPaper
|
|
|
|
Co-authored-by: Altiami <yoshimo.kristin@gmail.com>
|
|
Co-authored-by: Taiyou06 <kaandindar21@gmail.com>
|
|
Co-authored-by: MrlingXD <90316914+wling-art@users.noreply.github.com>
|
|
|
|
Commit: e2e154fb764650c90bbd23c9cede2b11ba1a08e2
|
|
|
|
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..9ad7e85d3fc58725a99e0cc8a487d047b7a493db 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);
|
|
+ private static final boolean HARD_THROW = !org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled || !org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.disableHardThrow; // Leaf - 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) // Leaf - SparklyPaper - parallel world ticking
|
|
throw new IllegalStateException(reason);
|
|
}
|
|
}
|
|
@@ -34,8 +36,11 @@ public class TickThread extends Thread {
|
|
if (!isTickThreadFor(world, pos)) {
|
|
final String ex = "Thread failed main thread check: " +
|
|
reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos;
|
|
- LOGGER.error(ex, new Throwable());
|
|
- throw new IllegalStateException(ex);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ final String threadInfo = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? ex + " - " + getTickThreadInformation(world.getServer()) : ex;
|
|
+ LOGGER.error(threadInfo, new Throwable());
|
|
+ if (HARD_THROW) throw new IllegalStateException(threadInfo);
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
}
|
|
|
|
@@ -43,8 +48,11 @@ public class TickThread extends Thread {
|
|
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;
|
|
- LOGGER.error(ex, new Throwable());
|
|
- throw new IllegalStateException(ex);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ final String threadInfo = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? ex + " - " + getTickThreadInformation(world.getServer()) : ex;
|
|
+ LOGGER.error(threadInfo, new Throwable());
|
|
+ if (HARD_THROW) throw new IllegalStateException(threadInfo);
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
}
|
|
|
|
@@ -52,8 +60,11 @@ public class TickThread extends Thread {
|
|
if (!isTickThreadFor(world, pos)) {
|
|
final String ex = "Thread failed main thread check: " +
|
|
reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + pos;
|
|
- LOGGER.error(ex, new Throwable());
|
|
- throw new IllegalStateException(ex);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ final String threadInfo = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? ex + " - " + getTickThreadInformation(world.getServer()) : ex;
|
|
+ LOGGER.error(threadInfo, new Throwable());
|
|
+ if (HARD_THROW) throw new IllegalStateException(threadInfo);
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
}
|
|
|
|
@@ -61,8 +72,11 @@ public class TickThread extends Thread {
|
|
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);
|
|
- LOGGER.error(ex, new Throwable());
|
|
- throw new IllegalStateException(ex);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ final String threadInfo = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? ex + " - " + getTickThreadInformation(world.getServer()) : ex;
|
|
+ LOGGER.error(threadInfo, new Throwable());
|
|
+ if (HARD_THROW) throw new IllegalStateException(threadInfo);
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
}
|
|
|
|
@@ -70,8 +84,11 @@ public class TickThread extends Thread {
|
|
if (!isTickThreadFor(entity)) {
|
|
final String ex = "Thread failed main thread check: " +
|
|
reason + ", context=" + getThreadContext() + ", entity=" + EntityUtil.dumpEntity(entity);
|
|
- LOGGER.error(ex, new Throwable());
|
|
- throw new IllegalStateException(ex);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ final String threadInfo = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? ex + " - " + getTickThreadInformation(entity.getServer()) : ex;
|
|
+ LOGGER.error(threadInfo, new Throwable());
|
|
+ if (HARD_THROW) throw new IllegalStateException(threadInfo);
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
}
|
|
|
|
@@ -79,8 +96,11 @@ public class TickThread extends Thread {
|
|
if (!isTickThreadFor(world, aabb)) {
|
|
final String ex = "Thread failed main thread check: " +
|
|
reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb;
|
|
- LOGGER.error(ex, new Throwable());
|
|
- throw new IllegalStateException(ex);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ final String threadInfo = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? ex + " - " + getTickThreadInformation(world.getServer()) : ex;
|
|
+ LOGGER.error(threadInfo, new Throwable());
|
|
+ if (HARD_THROW) throw new IllegalStateException(threadInfo);
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
}
|
|
|
|
@@ -88,11 +108,67 @@ public class TickThread extends Thread {
|
|
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);
|
|
- LOGGER.error(ex, new Throwable());
|
|
- throw new IllegalStateException(ex);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ final String threadInfo = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? ex + " - " + getTickThreadInformation(world.getServer()) : ex;
|
|
+ LOGGER.error(threadInfo, new Throwable());
|
|
+ if (HARD_THROW) throw new IllegalStateException(threadInfo);
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
}
|
|
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ // Copied from `TickThread#ensureTickThread(final String reason)`
|
|
+ // 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)) {
|
|
+ final String threadInfo = "Thread failed main thread check: " + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + " - " + getTickThreadInformation(world.getServer());
|
|
+ LOGGER.error(threadInfo, new Throwable());
|
|
+ if (HARD_THROW) throw new IllegalStateException(threadInfo);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // 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) {
|
|
+ if (!isTickThread() || isServerLevelTickThread()) {
|
|
+ final String threadInfo = "Thread failed tick-thread-only check: " + reason + ", context=" + getThreadContext();
|
|
+ LOGGER.error(threadInfo, new Throwable());
|
|
+ if (HARD_THROW) throw new IllegalStateException(threadInfo);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // 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) {
|
|
+ if (isTickThread() && !isTickThreadFor(world)) {
|
|
+ final String threadInfo = "Thread failed main thread or async thread check: " + reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + " - " + getTickThreadInformation(world.getServer());
|
|
+ LOGGER.error(threadInfo, new Throwable());
|
|
+ if (HARD_THROW) throw new IllegalStateException(threadInfo);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private 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: ");
|
|
+ sb.append(serverLevelTickThread.currentTickingServerLevel != null ? serverLevelTickThread.currentTickingServerLevel.getWorld().getName() : "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;
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - 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();
|
|
@@ -127,46 +203,70 @@ public class TickThread extends Thread {
|
|
}
|
|
|
|
public static boolean isTickThreadFor(final Level world, final BlockPos pos) {
|
|
- return isTickThread();
|
|
+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for
|
|
}
|
|
|
|
public static boolean isTickThreadFor(final Level world, final BlockPos pos, final int blockRadius) {
|
|
- return isTickThread();
|
|
+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for
|
|
}
|
|
|
|
public static boolean isTickThreadFor(final Level world, final ChunkPos pos) {
|
|
- return isTickThread();
|
|
+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for
|
|
}
|
|
|
|
public static boolean isTickThreadFor(final Level world, final Vec3 pos) {
|
|
- return isTickThread();
|
|
+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for
|
|
}
|
|
|
|
public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ) {
|
|
- return isTickThread();
|
|
+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for
|
|
}
|
|
|
|
public static boolean isTickThreadFor(final Level world, final AABB aabb) {
|
|
- return isTickThread();
|
|
+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for
|
|
}
|
|
|
|
public static boolean isTickThreadFor(final Level world, final double blockX, final double blockZ) {
|
|
- return isTickThread();
|
|
+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for
|
|
}
|
|
|
|
public static boolean isTickThreadFor(final Level world, final Vec3 position, final Vec3 deltaMovement, final int buffer) {
|
|
- return isTickThread();
|
|
+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for
|
|
}
|
|
|
|
public static boolean isTickThreadFor(final Level world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) {
|
|
- return isTickThread();
|
|
+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for
|
|
}
|
|
|
|
public static boolean isTickThreadFor(final Level world, final int chunkX, final int chunkZ, final int radius) {
|
|
- return isTickThread();
|
|
+ return isTickThreadFor(world); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for
|
|
}
|
|
|
|
public static boolean isTickThreadFor(final Entity entity) {
|
|
+ return isTickThreadFor(entity.level()); // Leaf - SparklyPaper - parallel world ticking - use methods for what they were made for
|
|
+ }
|
|
+
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ // This is an additional method to check if the tick thread is bound to a specific world because, by default, Paper's isTickThread methods do not provide this information
|
|
+ // Because we only tick worlds in parallel (instead of regions), we can use this for our checks
|
|
+ public static boolean isTickThreadFor(final Level world) {
|
|
+ if (Thread.currentThread() instanceof ServerLevelTickThread serverLevelTickThread) {
|
|
+ return serverLevelTickThread.currentTickingServerLevel == world;
|
|
+ }
|
|
return isTickThread();
|
|
}
|
|
+
|
|
+ public static class ServerLevelTickThread extends TickThread {
|
|
+
|
|
+ public net.minecraft.server.level.ServerLevel currentTickingServerLevel;
|
|
+
|
|
+ public ServerLevelTickThread(String name) {
|
|
+ super(name);
|
|
+ }
|
|
+
|
|
+ public ServerLevelTickThread(Runnable run, String name) {
|
|
+ super(run, name);
|
|
+ }
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
index 61121d2efd0df2fcafdc4c272e1cd1b986f42e24..0b63d9c6c71d5cdc791fca4705622b5d3546b8b5 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
@@ -480,7 +480,13 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|
}
|
|
|
|
private boolean unloadChunk0(int x, int z, boolean save) {
|
|
- org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot unload chunk asynchronously"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
|
+ } else {
|
|
+ org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
if (!this.isChunkLoaded(x, z)) {
|
|
return true;
|
|
}
|
|
@@ -497,6 +503,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|
|
|
@Override
|
|
public boolean refreshChunk(int x, int z) {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot refresh chunk asynchronously"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
|
ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
|
|
if (playerChunk == null) return false;
|
|
|
|
@@ -547,7 +554,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
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), x, z, "May not sync load chunks asynchronously"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
|
+ } else {
|
|
+ org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - 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
|
|
|
|
@@ -775,6 +788,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|
|
|
@Override
|
|
public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, loc.getX(), loc.getZ(), "Cannot generate tree asynchronously"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
|
this.world.captureTreeGeneration = true;
|
|
this.world.captureBlockStates = true;
|
|
boolean grownTree = this.generateTree(loc, type);
|
|
@@ -890,6 +904,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<net.minecraft.world.level.ServerExplosion> configurator) {
|
|
// Paper end - expand explosion API
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot create explosion asynchronously"); // Leaf - 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
|
|
@@ -981,6 +996,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|
|
|
@Override
|
|
public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x >> 4, z >> 4, "Cannot retrieve chunk asynchronously"); // Leaf - 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);
|
|
@@ -1011,6 +1027,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|
@Override
|
|
public void setBiome(int x, int y, int z, Holder<net.minecraft.world.level.biome.Biome> bb) {
|
|
BlockPos pos = new BlockPos(x, 0, z);
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, pos, "Cannot retrieve chunk asynchronously"); // Leaf - SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
|
if (this.world.hasChunkAt(pos)) {
|
|
net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkAt(pos);
|
|
|
|
@@ -2319,6 +2336,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|
|
|
@Override
|
|
public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, position.getX(), position.getZ(), "Cannot send game event asynchronously"); // Leaf - 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 dd122bbbe2c33183017dbde6997d3f1cd08479b5..72afe15ff95a4674f336168bbb0e14e5f6775106 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
|
|
@@ -74,12 +74,117 @@ public class CraftBlock implements Block {
|
|
return new CraftBlock(world, position);
|
|
}
|
|
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ private ServerLevel getServerLevel() {
|
|
+ return this.world instanceof ServerLevel serverLevel ? serverLevel : null;
|
|
+ }
|
|
+
|
|
+ private boolean needsBuffering(ServerLevel level, String handlingMode) {
|
|
+ // No ServerLevel means no queue, can't buffer
|
|
+ return level != null && org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && handlingMode.equals("BUFFERED") && !ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(level);
|
|
+ }
|
|
+
|
|
+ private void checkStrictMode(ServerLevel level, String handlingMode, String methodName) {
|
|
+ // Only check if PWT enabled, mode is STRICT, and we have a ServerLevel
|
|
+ if (level != null && org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && handlingMode.equals("STRICT")) {
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, this.position, "PWT: Async unsafe block read (strict mode): " + methodName);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private <T> T executeBufferedRead(ServerLevel level, org.dreeam.leaf.async.world.ReadOperationType type, Object[] params, T defaultValue, String methodName) {
|
|
+ if (level == null) { // Should not happen if called correctly after needsBuffering
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: executeBufferedRead called with null ServerLevel for " + methodName);
|
|
+ return defaultValue;
|
|
+ }
|
|
+
|
|
+ // Leaf start - SparklyPaper - parallel world ticking - Deadlock prevention
|
|
+ if (Thread.currentThread() instanceof ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread) {
|
|
+ try {
|
|
+ org.dreeam.leaf.async.world.WorldReadRequest request = new org.dreeam.leaf.async.world.WorldReadRequest(type, params, null);
|
|
+ Object result = level.executeReadRequest(request);
|
|
+ return (T) result;
|
|
+ } catch (Exception e) {
|
|
+ return defaultValue;
|
|
+ }
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking - Deadlock prevention
|
|
+
|
|
+ java.util.concurrent.CompletableFuture<Object> future = new java.util.concurrent.CompletableFuture<>();
|
|
+
|
|
+ // Leaf start - SparklyPaper - parallel world ticking - Shutdown handling for async reads
|
|
+ if (level.isShuttingDown()) {
|
|
+ future.completeExceptionally(new IllegalStateException("World " + level.getWorld().getName() + " is shutting down. Cannot queue new buffered read: " + type));
|
|
+ } else {
|
|
+ org.dreeam.leaf.async.world.WorldReadRequest request = new org.dreeam.leaf.async.world.WorldReadRequest(type, params, future);
|
|
+ level.asyncReadRequestQueue.offer(request); // Assumes queue exists on ServerLevel
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking - Shutdown handling for async reads
|
|
+
|
|
+ try {
|
|
+ Object result = future.join(); // Block until tick thread completes it
|
|
+ if (result == null) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Buffered async read returned null for " + methodName + " - returning default.");
|
|
+ return defaultValue;
|
|
+ }
|
|
+ return (T) result;
|
|
+ } catch (java.util.concurrent.CompletionException e) {
|
|
+ // Leaf start - SparklyPaper - parallel world ticking - Shutdown handling for async reads
|
|
+ if (e.getCause() instanceof IllegalStateException && e.getCause().getMessage() != null && e.getCause().getMessage().contains("shutting down")) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Async block read for " + methodName + " cancelled due to world shutdown: " + e.getCause().getMessage());
|
|
+ } else {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Async block read failed for " + methodName + " on tick thread.", e.getCause() != null ? e.getCause() : e);
|
|
+ }
|
|
+ return defaultValue; // Return default or rethrow if appropriate for your error handling strategy
|
|
+ // Leaf end - SparklyPaper - parallel world ticking - Shutdown handling for async reads
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Unexpected error during async block read for " + methodName, e);
|
|
+ return defaultValue;
|
|
+ }
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
+
|
|
public net.minecraft.world.level.block.state.BlockState getNMS() {
|
|
- return this.world.getBlockState(this.position);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+
|
|
+ if (needsBuffering(level, handlingMode)) {
|
|
+ // Buffered path
|
|
+ return executeBufferedRead(level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_GET_NMS_STATE, new Object[]{this.position}, Blocks.AIR.defaultBlockState(), "getNMS");
|
|
+ } else {
|
|
+ // Strict/Disabled/Non-ServerLevel path
|
|
+ checkStrictMode(level, handlingMode, "getNMS");
|
|
+ try {
|
|
+ return this.world.getBlockState(this.position);
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getNMS" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+ return Blocks.AIR.defaultBlockState();
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ return this.world.getBlockState(this.position);
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
public net.minecraft.world.level.material.FluidState getNMSFluid() {
|
|
- return this.world.getFluidState(this.position);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+ checkStrictMode(level, handlingMode, "getNMSFluid");
|
|
+
|
|
+ try {
|
|
+ return this.world.getFluidState(this.position);
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getNMSFluid" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+ return net.minecraft.world.level.material.Fluids.EMPTY.defaultFluidState();
|
|
+ }
|
|
+ } else {
|
|
+ return this.world.getFluidState(this.position);
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
public BlockPos getPosition() {
|
|
@@ -142,10 +247,12 @@ public class CraftBlock implements Block {
|
|
return this.getWorld().getChunkAt(this);
|
|
}
|
|
|
|
+ @Deprecated // Leaf - SparklyPaper - parallel world ticking - Magic value
|
|
public void setData(final byte data) {
|
|
this.setData(data, net.minecraft.world.level.block.Block.UPDATE_ALL);
|
|
}
|
|
|
|
+ @Deprecated // Leaf - SparklyPaper - parallel world ticking - Magic value
|
|
public void setData(final byte data, boolean applyPhysics) {
|
|
if (applyPhysics) {
|
|
this.setData(data, net.minecraft.world.level.block.Block.UPDATE_ALL);
|
|
@@ -155,12 +262,14 @@ public class CraftBlock implements Block {
|
|
}
|
|
|
|
private void setData(final byte data, int flags) {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); // Leaf - SparklyPaper - parallel world ticking
|
|
this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flags);
|
|
}
|
|
|
|
@Override
|
|
+ @Deprecated // Leaf - SparklyPaper - parallel world ticking - Magic value
|
|
public byte getData() {
|
|
- net.minecraft.world.level.block.state.BlockState state = this.world.getBlockState(this.position);
|
|
+ net.minecraft.world.level.block.state.BlockState state = this.getNMS(); // Leaf - SparklyPaper - parallel world ticking
|
|
return CraftMagicNumbers.toLegacyData(state);
|
|
}
|
|
|
|
@@ -177,6 +286,7 @@ public class CraftBlock implements Block {
|
|
@Override
|
|
public void setType(Material type, boolean applyPhysics) {
|
|
Preconditions.checkArgument(type != null, "Material cannot be null");
|
|
+ // Leaf - SparklyPaper - parallel world ticking - Delegates to setBlockData, which delegates to setTypeAndData (which has checks)
|
|
this.setBlockData(type.createBlockData(), applyPhysics);
|
|
}
|
|
|
|
@@ -196,6 +306,7 @@ 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) {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, pos, "Cannot modify world asynchronously"); // Leaf - SparklyPaper - 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
|
|
@@ -227,22 +338,74 @@ public class CraftBlock implements Block {
|
|
|
|
@Override
|
|
public Material getType() {
|
|
- return this.world.getBlockState(this.position).getBukkitMaterial(); // Paper - optimise getType calls
|
|
+ return this.getNMS().getBukkitMaterial(); // Paper - optimise getType calls // Leaf - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
public byte getLightLevel() {
|
|
- return (byte) this.world.getMinecraftWorld().getMaxLocalRawBrightness(this.position);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+ checkStrictMode(level, handlingMode, "getLightLevel"); // Strict check if applicable
|
|
+
|
|
+ try {
|
|
+ // Requires Level for getMaxLocalRawBrightness
|
|
+ if (this.world instanceof net.minecraft.world.level.Level nmsLevel) {
|
|
+ return (byte) nmsLevel.getMaxLocalRawBrightness(this.position);
|
|
+ }
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: getLightLevel called on non-Level - returning 0");
|
|
+ return 0;
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getLightLevel" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+ return 0;
|
|
+ }
|
|
+ } else {
|
|
+ return (byte) this.world.getMinecraftWorld().getMaxLocalRawBrightness(this.position);
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
public byte getLightFromSky() {
|
|
- return (byte) this.world.getBrightness(LightLayer.SKY, this.position);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+ checkStrictMode(level, handlingMode, "getLightFromSky"); // Strict check if applicable
|
|
+
|
|
+ try {
|
|
+ // LevelAccessor has getBrightness
|
|
+ return (byte) this.world.getBrightness(LightLayer.SKY, this.position);
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getLightFromSky" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+ return 0;
|
|
+ }
|
|
+ } else {
|
|
+ return (byte) this.world.getBrightness(LightLayer.SKY, this.position);
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
public byte getLightFromBlocks() {
|
|
- return (byte) this.world.getBrightness(LightLayer.BLOCK, this.position);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+ checkStrictMode(level, handlingMode, "getLightFromBlocks"); // Strict check if applicable
|
|
+
|
|
+ try {
|
|
+ // LevelAccessor has getBrightness
|
|
+ return (byte) this.world.getBrightness(LightLayer.BLOCK, this.position);
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getLightFromBlocks" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+ return 0;
|
|
+ }
|
|
+ } else {
|
|
+ return (byte) this.world.getBrightness(LightLayer.BLOCK, this.position);
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
public Block getFace(final BlockFace face) {
|
|
@@ -344,18 +507,69 @@ public class CraftBlock implements Block {
|
|
|
|
@Override
|
|
public Biome getBiome() {
|
|
- return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ());
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+
|
|
+ if (needsBuffering(level, handlingMode)) {
|
|
+ // Buffered path
|
|
+ net.minecraft.core.Holder<net.minecraft.world.level.biome.Biome> nmsResult = executeBufferedRead(
|
|
+ level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_GET_BIOME, new Object[]{this.position}, null, "getBiome"
|
|
+ );
|
|
+ return (nmsResult != null) ? CraftBiome.minecraftHolderToBukkit(nmsResult) : Biome.PLAINS; // Default biome
|
|
+ } else {
|
|
+ // Strict/Disabled/Non-ServerLevel path
|
|
+ checkStrictMode(level, handlingMode, "getBiome");
|
|
+ try {
|
|
+ // Use Bukkit API, which should delegate safely (or buffer itself if needed)
|
|
+ return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ());
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getBiome" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+ return Biome.PLAINS; // Default biome on error
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ());
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
// Paper start
|
|
@Override
|
|
public Biome getComputedBiome() {
|
|
- return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ());
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+
|
|
+ if (needsBuffering(level, handlingMode)) {
|
|
+ // Buffered path
|
|
+ net.minecraft.core.Holder<net.minecraft.world.level.biome.Biome> nmsResult = executeBufferedRead(
|
|
+ level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_GET_COMPUTED_BIOME, new Object[]{this.position}, null, "getComputedBiome"
|
|
+ );
|
|
+ return (nmsResult != null) ? CraftBiome.minecraftHolderToBukkit(nmsResult) : Biome.PLAINS; // Default biome
|
|
+ } else {
|
|
+ // Strict/Disabled/Non-ServerLevel path
|
|
+ checkStrictMode(level, handlingMode, "getComputedBiome");
|
|
+ try {
|
|
+ // Use Bukkit API
|
|
+ return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ());
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getComputedBiome" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+ return Biome.PLAINS; // Default biome on error
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ());
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
// Paper end
|
|
|
|
@Override
|
|
public void setBiome(Biome bio) {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously"); // Leaf - SparklyPaper - parallel world ticking
|
|
this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio);
|
|
}
|
|
|
|
@@ -371,12 +585,58 @@ public class CraftBlock implements Block {
|
|
|
|
@Override
|
|
public boolean isBlockPowered() {
|
|
- return this.world.getMinecraftWorld().getDirectSignalTo(this.position) > 0;
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+ checkStrictMode(level, handlingMode, "isBlockPowered"); // Strict check if applicable
|
|
+
|
|
+ try {
|
|
+ // Requires Level.getDirectSignalTo
|
|
+ if (this.world instanceof net.minecraft.world.level.Level nmsLevel) {
|
|
+ return nmsLevel.getDirectSignalTo(this.position) > 0;
|
|
+ }
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: isBlockPowered called on non-Level - returning false");
|
|
+ return false;
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for isBlockPowered" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+ return false;
|
|
+ }
|
|
+ } else {
|
|
+ return this.world.getMinecraftWorld().getDirectSignalTo(this.position) > 0;
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
public boolean isBlockIndirectlyPowered() {
|
|
- return this.world.getMinecraftWorld().hasNeighborSignal(this.position);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+
|
|
+ if (needsBuffering(level, handlingMode)) {
|
|
+ // Buffered path
|
|
+ return executeBufferedRead(level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_IS_INDIRECTLY_POWERED, new Object[]{this.position}, false, "isBlockIndirectlyPowered");
|
|
+ } else {
|
|
+ // Strict/Disabled/Non-ServerLevel path
|
|
+ checkStrictMode(level, handlingMode, "isBlockIndirectlyPowered");
|
|
+ try {
|
|
+ // Requires Level.hasNeighborSignal
|
|
+ if (this.world instanceof net.minecraft.world.level.Level nmsLevel) {
|
|
+ return nmsLevel.hasNeighborSignal(this.position);
|
|
+ }
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: isBlockIndirectlyPowered called on non-Level - returning false");
|
|
+ return false;
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for isBlockIndirectlyPowered" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ return this.world.getMinecraftWorld().hasNeighborSignal(this.position);
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
@@ -398,38 +658,40 @@ public class CraftBlock implements Block {
|
|
|
|
@Override
|
|
public boolean isBlockFacePowered(BlockFace face) {
|
|
- return this.world.getMinecraftWorld().hasSignal(this.position, CraftBlock.blockFaceToNotch(face));
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+ checkStrictMode(level, handlingMode, "isBlockFacePowered"); // Strict check if applicable
|
|
+
|
|
+ try {
|
|
+ if (this.world instanceof net.minecraft.world.level.Level nmsLevel) {
|
|
+ return nmsLevel.hasSignal(this.position, CraftBlock.blockFaceToNotch(face));
|
|
+ }
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: isBlockFacePowered called on non-Level - returning false");
|
|
+ return false;
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for isBlockFacePowered" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+ return false;
|
|
+ }
|
|
+ } else {
|
|
+ return this.world.getMinecraftWorld().hasSignal(this.position, CraftBlock.blockFaceToNotch(face));
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
public boolean isBlockFaceIndirectlyPowered(BlockFace face) {
|
|
- int power = this.world.getMinecraftWorld().getSignal(this.position, CraftBlock.blockFaceToNotch(face));
|
|
-
|
|
- Block relative = this.getRelative(face);
|
|
- if (relative.getType() == Material.REDSTONE_WIRE) {
|
|
- return Math.max(power, relative.getData()) > 0; // todo remove legacy usage
|
|
- }
|
|
-
|
|
- return power > 0;
|
|
+ return pwt$isBlockFaceIndirectlyPowered(face); // Leaf - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
public int getBlockPower(BlockFace face) {
|
|
- int power = 0;
|
|
- net.minecraft.world.level.Level world = this.world.getMinecraftWorld();
|
|
- int x = this.getX();
|
|
- int y = this.getY();
|
|
- int z = this.getZ();
|
|
- if ((face == BlockFace.DOWN || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x, y - 1, z), Direction.DOWN)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x, y - 1, z)));
|
|
- if ((face == BlockFace.UP || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x, y + 1, z), Direction.UP)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x, y + 1, z)));
|
|
- if ((face == BlockFace.EAST || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x + 1, y, z), Direction.EAST)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x + 1, y, z)));
|
|
- if ((face == BlockFace.WEST || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x - 1, y, z), Direction.WEST)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x - 1, y, z)));
|
|
- if ((face == BlockFace.NORTH || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x, y, z - 1), Direction.NORTH)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x, y, z - 1)));
|
|
- if ((face == BlockFace.SOUTH || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x, y, z + 1), Direction.SOUTH)) power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x, y, z + 1)));
|
|
- return power > 0 ? power : (face == BlockFace.SELF ? this.isBlockIndirectlyPowered() : this.isBlockFaceIndirectlyPowered(face)) ? 15 : 0;
|
|
- }
|
|
-
|
|
- private static int getPower(int power, net.minecraft.world.level.block.state.BlockState state) {
|
|
+ return pwt$getBlockPower(face); // Leaf - SparklyPaper - parallel world ticking
|
|
+ }
|
|
+
|
|
+ // Leaf - SparklyPaper - parallel world ticking - Static helper, safe
|
|
+ public static int getPower(int power, net.minecraft.world.level.block.state.BlockState state) { // Leaf - SparklyPaper - parallel world ticking - private -> public
|
|
if (!state.is(Blocks.REDSTONE_WIRE)) {
|
|
return power;
|
|
} else {
|
|
@@ -478,23 +740,37 @@ public class CraftBlock implements Block {
|
|
|
|
@Override
|
|
public PistonMoveReaction getPistonMoveReaction() {
|
|
+ // Leaf - SparklyPaper - parallel world ticking - Uses safe getNMS()
|
|
return PistonMoveReaction.getById(this.getNMS().getPistonPushReaction().ordinal());
|
|
}
|
|
|
|
@Override
|
|
public boolean breakNaturally() {
|
|
- return this.breakNaturally(null);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking - Write operation check
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ServerLevel level = getServerLevel();
|
|
+ if (level != null) {
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, position, "PWT: Cannot modify world asynchronously (breakNaturally)");
|
|
+ } else {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Attempted breakNaturally on non-ServerLevel - safety not guaranteed.");
|
|
+ }
|
|
+ // Delegates to overload
|
|
+ return this.breakNaturally(null, true, true); // Default to dropping XP and triggering effects
|
|
+ } else {
|
|
+ return this.breakNaturally(null);
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking - Write operation check
|
|
}
|
|
|
|
@Override
|
|
public boolean breakNaturally(ItemStack item) {
|
|
// Paper start
|
|
- return this.breakNaturally(item, false);
|
|
+ return org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? this.breakNaturally(item, true, true) : this.breakNaturally(item, false); // Leaf - SparklyPaper - parallel world ticking - Delegates to full overload
|
|
}
|
|
|
|
@Override
|
|
public boolean breakNaturally(boolean triggerEffect, boolean dropExperience) {
|
|
- return this.breakNaturally(null, triggerEffect, dropExperience);
|
|
+ return this.breakNaturally(null, triggerEffect, dropExperience);// Leaf - SparklyPaper - parallel world ticking - Delegates to full overload
|
|
}
|
|
|
|
@Override
|
|
@@ -505,85 +781,12 @@ public class CraftBlock implements Block {
|
|
@Override
|
|
public boolean breakNaturally(ItemStack item, boolean triggerEffect, boolean dropExperience, boolean forceEffect) {
|
|
// 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();
|
|
- net.minecraft.world.level.block.Block block = state.getBlock();
|
|
- net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item);
|
|
- boolean result = false;
|
|
-
|
|
- // Modelled off Player#hasCorrectToolForDrops
|
|
- if (block != Blocks.AIR && (item == null || !state.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(state))) {
|
|
- net.minecraft.world.level.block.Block.dropResources(state, this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), null, nmsItem, false); // Paper - Properly handle xp dropping
|
|
- // Paper start - improve Block#breakNaturally
|
|
- if (dropExperience) block.popExperience(this.world.getMinecraftWorld(), this.position, block.getExpDrop(state, this.world.getMinecraftWorld(), this.position, nmsItem, true));
|
|
- // Paper end
|
|
- result = true;
|
|
- }
|
|
-
|
|
- if ((result && triggerEffect) || (forceEffect && block != Blocks.AIR)) {
|
|
- if (state.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock) {
|
|
- this.world.levelEvent(net.minecraft.world.level.block.LevelEvent.SOUND_EXTINGUISH_FIRE, this.position, 0);
|
|
- } else {
|
|
- this.world.levelEvent(net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK, this.position, net.minecraft.world.level.block.Block.getId(state));
|
|
- }
|
|
- }
|
|
-
|
|
- // SPIGOT-6778: Directly call setBlock instead of setBlockState, so that the block entity is not removed and custom remove logic is run.
|
|
- // Paper start - improve breakNaturally
|
|
- boolean destroyed = this.world.removeBlock(this.position, false);
|
|
- if (destroyed) {
|
|
- block.destroy(this.world, this.position, state);
|
|
- }
|
|
- if (result) {
|
|
- // special cases
|
|
- if (block instanceof net.minecraft.world.level.block.IceBlock iceBlock) {
|
|
- iceBlock.afterDestroy(this.world.getMinecraftWorld(), this.position, nmsItem);
|
|
- } else if (block instanceof net.minecraft.world.level.block.TurtleEggBlock turtleEggBlock) {
|
|
- turtleEggBlock.decreaseEggs(this.world.getMinecraftWorld(), this.position, state);
|
|
- }
|
|
- }
|
|
- return destroyed && result;
|
|
- // Paper end
|
|
+ return pwt$breakNaturally(item, triggerEffect, dropExperience, forceEffect); // Leaf - SparklyPaper - parallel world ticking - Write operation check
|
|
}
|
|
|
|
@Override
|
|
public boolean applyBoneMeal(BlockFace face) {
|
|
- Direction direction = CraftBlock.blockFaceToNotch(face);
|
|
- BlockFertilizeEvent event = null;
|
|
- ServerLevel world = this.getCraftWorld().getHandle();
|
|
- UseOnContext context = new UseOnContext(world, null, InteractionHand.MAIN_HAND, Items.BONE_MEAL.getDefaultInstance(), new BlockHitResult(Vec3.ZERO, direction, this.getPosition(), false));
|
|
-
|
|
- // SPIGOT-6895: Call StructureGrowEvent and BlockFertilizeEvent
|
|
- world.captureTreeGeneration = true;
|
|
- InteractionResult result = BoneMealItem.applyBonemeal(context);
|
|
- world.captureTreeGeneration = false;
|
|
-
|
|
- if (!world.capturedBlockStates.isEmpty()) {
|
|
- TreeType treeType = SaplingBlock.treeType;
|
|
- SaplingBlock.treeType = null;
|
|
- List<BlockState> states = new ArrayList<>(world.capturedBlockStates.values());
|
|
- world.capturedBlockStates.clear();
|
|
- StructureGrowEvent structureEvent = null;
|
|
-
|
|
- if (treeType != null) {
|
|
- structureEvent = new StructureGrowEvent(this.getLocation(), treeType, true, null, states);
|
|
- Bukkit.getPluginManager().callEvent(structureEvent);
|
|
- }
|
|
-
|
|
- event = new BlockFertilizeEvent(CraftBlock.at(world, this.getPosition()), null, states);
|
|
- event.setCancelled(structureEvent != null && structureEvent.isCancelled());
|
|
- Bukkit.getPluginManager().callEvent(event);
|
|
-
|
|
- if (!event.isCancelled()) {
|
|
- for (BlockState state : states) {
|
|
- CraftBlockState craftBlockState = (CraftBlockState) state;
|
|
- craftBlockState.place(craftBlockState.getFlags());
|
|
- world.checkCapturedTreeStateForObserverNotify(this.position, craftBlockState); // Paper - notify observers even if grow failed
|
|
- }
|
|
- }
|
|
- }
|
|
-
|
|
- return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled());
|
|
+ return pwt$applyBoneMeal(face); // Leaf - SparklyPaper - parallel world ticking - Write operation check
|
|
}
|
|
|
|
@Override
|
|
@@ -598,20 +801,12 @@ public class CraftBlock implements Block {
|
|
|
|
@Override
|
|
public Collection<ItemStack> getDrops(ItemStack item, Entity entity) {
|
|
- net.minecraft.world.level.block.state.BlockState state = this.getNMS();
|
|
- net.minecraft.world.item.ItemStack nms = CraftItemStack.asNMSCopy(item);
|
|
-
|
|
- // Modelled off Player#hasCorrectToolForDrops
|
|
- if (item == null || CraftBlockData.isPreferredTool(state, nms)) {
|
|
- return net.minecraft.world.level.block.Block.getDrops(state, this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), entity == null ? null : ((CraftEntity) entity).getHandle(), nms)
|
|
- .stream().map(CraftItemStack::asBukkitCopy).collect(Collectors.toList());
|
|
- } else {
|
|
- return Collections.emptyList();
|
|
- }
|
|
+ return pwt$getDrops(item, entity); // Leaf - SparklyPaper - parallel world ticking - Write operation check
|
|
}
|
|
|
|
@Override
|
|
public boolean isPreferredTool(ItemStack item) {
|
|
+ // Leaf - SparklyPaper - parallel world ticking - Uses safe getNMS()
|
|
net.minecraft.world.level.block.state.BlockState state = this.getNMS();
|
|
net.minecraft.world.item.ItemStack nms = CraftItemStack.asNMSCopy(item);
|
|
return CraftBlockData.isPreferredTool(state, nms);
|
|
@@ -620,9 +815,27 @@ public class CraftBlock implements Block {
|
|
@Override
|
|
public float getBreakSpeed(Player player) {
|
|
Preconditions.checkArgument(player != null, "player cannot be null");
|
|
- return this.getNMS().getDestroyProgress(((CraftPlayer) player).getHandle(), this.world, this.position);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ // Requires LevelReader (LevelAccessor is sufficient)
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+ checkStrictMode(level, handlingMode, "getBreakSpeed"); // Strict check if applicable
|
|
+
|
|
+ try {
|
|
+ // Uses safe getNMS()
|
|
+ return this.getNMS().getDestroyProgress(((CraftPlayer) player).getHandle(), this.world, this.position);
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getBreakSpeed" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+ return 0.0f;
|
|
+ }
|
|
+ } else {
|
|
+ return this.getNMS().getDestroyProgress(((CraftPlayer) player).getHandle(), this.world, this.position);
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
+ // Leaf - SparklyPaper - parallel world ticking - Metadata methods delegate to CraftWorld, assumed safe
|
|
@Override
|
|
public void setMetadata(String metadataKey, MetadataValue newMetadataValue) {
|
|
this.getCraftWorld().getBlockMetadata().setMetadata(this, metadataKey, newMetadataValue);
|
|
@@ -645,7 +858,25 @@ public class CraftBlock implements Block {
|
|
|
|
@Override
|
|
public boolean isPassable() {
|
|
- return this.getNMS().getCollisionShape(this.world, this.position).isEmpty();
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ // Requires LevelReader (LevelAccessor is sufficient)
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+ checkStrictMode(level, handlingMode, "isPassable"); // Strict check if applicable
|
|
+
|
|
+ try {
|
|
+ // Uses safe getNMS()
|
|
+ VoxelShape shape = this.getNMS().getCollisionShape(this.world, this.position);
|
|
+ return shape.isEmpty();
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for isPassable" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+ return true; // Default to passable on error? Or false? Passable seems safer.
|
|
+ }
|
|
+ } else {
|
|
+ return this.getNMS().getCollisionShape(this.world, this.position).isEmpty();
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
@@ -663,39 +894,68 @@ public class CraftBlock implements Block {
|
|
return null;
|
|
}
|
|
|
|
- Vector dir = direction.clone().normalize().multiply(maxDistance);
|
|
- Vec3 startPos = CraftLocation.toVec3(start);
|
|
- Vec3 endPos = startPos.add(dir.getX(), dir.getY(), dir.getZ());
|
|
-
|
|
- HitResult hitResult = this.world.clip(new ClipContext(startPos, endPos, ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toFluid(fluidCollisionMode), CollisionContext.empty()), this.position);
|
|
- return CraftRayTraceResult.convertFromInternal(this.world, hitResult);
|
|
+ return pwt$rayTrace(start, direction, maxDistance, fluidCollisionMode); // Leaf - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
public BoundingBox getBoundingBox() {
|
|
- VoxelShape shape = this.getNMS().getShape(this.world, this.position);
|
|
-
|
|
- if (shape.isEmpty()) {
|
|
- return new BoundingBox(); // Return an empty bounding box if the block has no dimension
|
|
- }
|
|
-
|
|
- AABB aabb = shape.bounds();
|
|
- return new BoundingBox(this.getX() + aabb.minX, this.getY() + aabb.minY, this.getZ() + aabb.minZ, this.getX() + aabb.maxX, this.getY() + aabb.maxY, this.getZ() + aabb.maxZ);
|
|
+ return pwt$getBoundingBox(); // Leaf - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
public org.bukkit.util.VoxelShape getCollisionShape() {
|
|
- VoxelShape shape = this.getNMS().getCollisionShape(this.world, this.position);
|
|
- return new CraftVoxelShape(shape);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ // Requires LevelReader (LevelAccessor is sufficient)
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+ checkStrictMode(level, handlingMode, "getCollisionShape"); // Strict check if applicable
|
|
+
|
|
+ try {
|
|
+ // Uses safe getNMS()
|
|
+ VoxelShape shape = this.getNMS().getCollisionShape(this.world, this.position);
|
|
+ return new CraftVoxelShape(shape); // Wrap NMS shape
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getCollisionShape" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+ return new CraftVoxelShape(net.minecraft.world.phys.shapes.Shapes.empty()); // Default to empty shape on error
|
|
+ }
|
|
+ } else {
|
|
+ VoxelShape shape = this.getNMS().getCollisionShape(this.world, this.position);
|
|
+ return new CraftVoxelShape(shape);
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
public boolean canPlace(BlockData data) {
|
|
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();
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+
|
|
+ if (needsBuffering(level, handlingMode)) {
|
|
+ // Buffered path
|
|
+ return executeBufferedRead(level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_CAN_PLACE, new Object[]{this.position, data}, false, "canPlace");
|
|
+ } else {
|
|
+ // Strict/Disabled/Non-ServerLevel path
|
|
+ checkStrictMode(level, handlingMode, "canPlace");
|
|
+ try {
|
|
+ net.minecraft.world.level.block.state.BlockState nmsData = ((CraftBlockData) data).getState();
|
|
+ // LevelAccessor has canSurvive
|
|
+ return nmsData.canSurvive(this.world, this.position);
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for canPlace" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+ return false; // Default to false on error
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ net.minecraft.world.level.block.state.BlockState iblockdata = ((CraftBlockData) data).getState();
|
|
+ net.minecraft.world.level.Level world = this.world.getMinecraftWorld();
|
|
|
|
- return iblockdata.canSurvive(world, this.position);
|
|
+ return iblockdata.canSurvive(world, this.position);
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
@@ -711,7 +971,10 @@ public class CraftBlock implements Block {
|
|
// Paper start
|
|
@Override
|
|
public com.destroystokyo.paper.block.BlockSoundGroup getSoundGroup() {
|
|
- return new com.destroystokyo.paper.block.CraftBlockSoundGroup(getNMS().getBlock().defaultBlockState().getSoundType());
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ net.minecraft.world.level.block.SoundType nmsSoundType = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled ? this.getNMS().getSoundType() : getNMS().getBlock().defaultBlockState().getSoundType();
|
|
+ return new com.destroystokyo.paper.block.CraftBlockSoundGroup(nmsSoundType);
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
@@ -730,20 +993,511 @@ public class CraftBlock implements Block {
|
|
|
|
@Override
|
|
public void tick() {
|
|
- final ServerLevel level = this.world.getMinecraftWorld();
|
|
+ // Leaf start - SparklyPaper - parallel world ticking - Write operation check
|
|
+ // (block ticks can modify state)
|
|
+ ServerLevel level;
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ level = getServerLevel();
|
|
+ if (level != null) {
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, position, "PWT: Cannot tick block asynchronously (tick)");
|
|
+ } else {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Attempted tick on non-ServerLevel - safety not guaranteed.");
|
|
+ return; // Cannot tick without ServerLevel
|
|
+ }
|
|
+ } else {
|
|
+ level = this.world.getMinecraftWorld();
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking -
|
|
+
|
|
this.getNMS().tick(level, this.position, level.random);
|
|
}
|
|
|
|
|
|
@Override
|
|
public void fluidTick() {
|
|
- this.getNMSFluid().tick(this.world.getMinecraftWorld(), this.position, this.getNMS());
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ // Fluid ticks can modify state
|
|
+ ServerLevel level;
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ level = getServerLevel();
|
|
+ if (level != null) {
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, position, "PWT: Cannot tick fluid asynchronously (fluidTick)");
|
|
+ } else {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Attempted fluidTick on non-ServerLevel - safety not guaranteed.");
|
|
+ return; // Cannot tick fluid without ServerLevel
|
|
+ }
|
|
+ } else {
|
|
+ level = this.world.getMinecraftWorld();
|
|
+ }
|
|
+ this.getNMSFluid().tick(level, this.position, this.getNMS());
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
public void randomTick() {
|
|
- final ServerLevel level = this.world.getMinecraftWorld();
|
|
+ // Leaf start - SparklyPaper - parallel world ticking - Write operation check
|
|
+ // (random ticks can modify state)
|
|
+ ServerLevel level;
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ level = getServerLevel();
|
|
+ if (level != null) {
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, position, "PWT: Cannot randomTick block asynchronously");
|
|
+ } else {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Attempted randomTick on non-ServerLevel - safety not guaranteed.");
|
|
+ return;
|
|
+ }
|
|
+ } else {
|
|
+ level = this.world.getMinecraftWorld();
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking - Write operation check
|
|
this.getNMS().randomTick(level, this.position, level.random);
|
|
}
|
|
// Paper end
|
|
+
|
|
+ private boolean pwt$isBlockFaceIndirectlyPowered(BlockFace face) {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+ checkStrictMode(level, handlingMode, "isBlockFaceIndirectlyPowered"); // Strict check if applicable
|
|
+
|
|
+ try {
|
|
+ // Requires Level.getSignal and potentially relative block access (which might need safety checks)
|
|
+ if (this.world instanceof net.minecraft.world.level.Level nmsLevel) {
|
|
+ int power = nmsLevel.getSignal(this.position, CraftBlock.blockFaceToNotch(face));
|
|
+ Block relative = this.getRelative(face); // getRelative delegates, safety depends on target block
|
|
+ if (relative.getType() == Material.REDSTONE_WIRE) {
|
|
+ // getData might need buffering if called on relative block
|
|
+ return Math.max(power, relative.getData()) > 0;
|
|
+ }
|
|
+ return power > 0;
|
|
+ }
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: isBlockFaceIndirectlyPowered called on non-Level - returning false");
|
|
+ return false;
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for isBlockFaceIndirectlyPowered" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+
|
|
+ return false;
|
|
+ }
|
|
+ } else {
|
|
+ int power = this.world.getMinecraftWorld().getSignal(this.position, CraftBlock.blockFaceToNotch(face));
|
|
+
|
|
+ Block relative = this.getRelative(face);
|
|
+ if (relative.getType() == Material.REDSTONE_WIRE) {
|
|
+ return Math.max(power, relative.getData()) > 0; // todo remove legacy usage
|
|
+ }
|
|
+
|
|
+ return power > 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private int pwt$getBlockPower(BlockFace face) {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+
|
|
+ if (needsBuffering(level, handlingMode)) {
|
|
+ // Buffered path
|
|
+ return executeBufferedRead(level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_GET_BLOCK_POWER, new Object[]{this.position, face}, 0, "getBlockPower");
|
|
+ } else {
|
|
+ // Strict/Disabled/Non-ServerLevel path
|
|
+ checkStrictMode(level, handlingMode, "getBlockPower");
|
|
+ try {
|
|
+ // Requires Level for hasSignal and getBlockState
|
|
+ if (this.world instanceof net.minecraft.world.level.Level nmsLevel) {
|
|
+ int power = 0;
|
|
+ BlockPos currentPos = this.position; // Use immutable position
|
|
+
|
|
+ // Check neighbors using relative positions
|
|
+ if ((face == BlockFace.DOWN || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.below(), Direction.DOWN))
|
|
+ power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.below()));
|
|
+ if ((face == BlockFace.UP || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.above(), Direction.UP))
|
|
+ power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.above()));
|
|
+ if ((face == BlockFace.EAST || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.east(), Direction.EAST))
|
|
+ power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.east()));
|
|
+ if ((face == BlockFace.WEST || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.west(), Direction.WEST))
|
|
+ power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.west()));
|
|
+ if ((face == BlockFace.NORTH || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.north(), Direction.NORTH))
|
|
+ power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.north()));
|
|
+ if ((face == BlockFace.SOUTH || face == BlockFace.SELF) && nmsLevel.hasSignal(currentPos.south(), Direction.SOUTH))
|
|
+ power = CraftBlock.getPower(power, nmsLevel.getBlockState(currentPos.south()));
|
|
+
|
|
+ // Need to call isBlockIndirectlyPowered/isBlockFaceIndirectlyPowered safely
|
|
+ boolean indirect = (face == BlockFace.SELF) ? this.isBlockIndirectlyPowered() : this.isBlockFaceIndirectlyPowered(face);
|
|
+ return power > 0 ? power : (indirect ? 15 : 0);
|
|
+ } else {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: getBlockPower called on non-Level - returning 0");
|
|
+ return 0;
|
|
+ }
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getBlockPower" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+ return 0;
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ int power = 0;
|
|
+ net.minecraft.world.level.Level world = this.world.getMinecraftWorld();
|
|
+ int x = this.getX();
|
|
+ int y = this.getY();
|
|
+ int z = this.getZ();
|
|
+ if ((face == BlockFace.DOWN || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x, y - 1, z), Direction.DOWN))
|
|
+ power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x, y - 1, z)));
|
|
+ if ((face == BlockFace.UP || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x, y + 1, z), Direction.UP))
|
|
+ power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x, y + 1, z)));
|
|
+ if ((face == BlockFace.EAST || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x + 1, y, z), Direction.EAST))
|
|
+ power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x + 1, y, z)));
|
|
+ if ((face == BlockFace.WEST || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x - 1, y, z), Direction.WEST))
|
|
+ power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x - 1, y, z)));
|
|
+ if ((face == BlockFace.NORTH || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x, y, z - 1), Direction.NORTH))
|
|
+ power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x, y, z - 1)));
|
|
+ if ((face == BlockFace.SOUTH || face == BlockFace.SELF) && world.hasSignal(new BlockPos(x, y, z + 1), Direction.SOUTH))
|
|
+ power = CraftBlock.getPower(power, world.getBlockState(new BlockPos(x, y, z + 1)));
|
|
+ return power > 0 ? power : (face == BlockFace.SELF ? this.isBlockIndirectlyPowered() : this.isBlockFaceIndirectlyPowered(face)) ? 15 : 0;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private boolean pwt$breakNaturally(ItemStack item, boolean triggerEffect, boolean dropExperience, boolean forceEffect) {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ // Order matters here, need to drop before setting to air so skulls can get their data
|
|
+ // (already done in simpler overload, but check again for safety)
|
|
+ ServerLevel level = getServerLevel();
|
|
+ if (level != null) {
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, position, "PWT: Cannot modify world asynchronously (breakNaturally item...)");
|
|
+ } else {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Attempted breakNaturally(item...) on non-ServerLevel - safety not guaranteed.");
|
|
+ }
|
|
+
|
|
+ // Get NMS state safely
|
|
+
|
|
+ net.minecraft.world.level.block.state.BlockState state = this.getNMS();
|
|
+ net.minecraft.world.level.block.Block block = state.getBlock();
|
|
+ net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item);
|
|
+ boolean droppedItems = false;
|
|
+
|
|
+ // Experience dropping requires ServerLevel
|
|
+ ServerLevel serverLevelForDrops = getServerLevel(); // Re-get ServerLevel specifically for drop logic
|
|
+
|
|
+ if (serverLevelForDrops != null) { // Only attempt drops/XP if we have a ServerLevel
|
|
+ // Check if block should drop items
|
|
+ if (!state.isAir() && (item == null || !state.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(state))) {
|
|
+ // Drop items using ServerLevel
|
|
+ net.minecraft.world.level.block.Block.dropResources(state, serverLevelForDrops, this.position, this.world.getBlockEntity(this.position), null, nmsItem, false);
|
|
+
|
|
+ // Drop experience using ServerLevel
|
|
+ if (dropExperience) {
|
|
+ int xp = block.getExpDrop(state, serverLevelForDrops, this.position, nmsItem, true);
|
|
+ if (xp > 0) { // Only pop if there's XP to drop
|
|
+ block.popExperience(serverLevelForDrops, this.position, xp);
|
|
+
|
|
+ }
|
|
+ }
|
|
+ droppedItems = true;
|
|
+ }
|
|
+ } else {
|
|
+ // Log if we couldn't drop XP because it wasn't a ServerLevel
|
|
+ if (dropExperience && !state.isAir()) { // Only warn if XP was requested and block wasn't air
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Cannot drop experience for breakNaturally: Not a ServerLevel.");
|
|
+ }
|
|
+ // Still trigger effects if requested and possible with LevelAccessor
|
|
+ if (triggerEffect && !state.isAir()) {
|
|
+ int eventId = (state.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock)
|
|
+ ? net.minecraft.world.level.block.LevelEvent.SOUND_EXTINGUISH_FIRE
|
|
+ : net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK;
|
|
+ int eventData = (eventId == net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK)
|
|
+ ? net.minecraft.world.level.block.Block.getId(state) : 0;
|
|
+ this.world.levelEvent(eventId, this.position, eventData);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Trigger effect using LevelAccessor (safe)
|
|
+ if ((droppedItems && triggerEffect) || (forceEffect && block != Blocks.AIR)) {
|
|
+ int eventId = (state.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock)
|
|
+ ? net.minecraft.world.level.block.LevelEvent.SOUND_EXTINGUISH_FIRE
|
|
+ : net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK;
|
|
+ int eventData = (eventId == net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK)
|
|
+ ? net.minecraft.world.level.block.Block.getId(state) : 0;
|
|
+ this.world.levelEvent(eventId, this.position, eventData);
|
|
+ }
|
|
+
|
|
+ // Remove the block using LevelAccessor (safe)
|
|
+ boolean destroyed = this.world.removeBlock(this.position, false);
|
|
+ if (destroyed) {
|
|
+ // Call destroy hook using LevelAccessor (safe)
|
|
+ block.destroy(this.world, this.position, state);
|
|
+ }
|
|
+ // Special case handling - requires Level, check again
|
|
+ if (droppedItems && this.world instanceof net.minecraft.world.level.Level nmsLevelForSpecialCases) {
|
|
+ if (block instanceof net.minecraft.world.level.block.IceBlock iceBlock) {
|
|
+ iceBlock.afterDestroy(nmsLevelForSpecialCases, this.position, nmsItem);
|
|
+ } else if (block instanceof net.minecraft.world.level.block.TurtleEggBlock turtleEggBlock) {
|
|
+ turtleEggBlock.decreaseEggs(nmsLevelForSpecialCases, this.position, state);
|
|
+ }
|
|
+ } else if (droppedItems) { // Log if special cases couldn't run
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Cannot perform post-break special actions: Not a Level.");
|
|
+ }
|
|
+ // Return true if the block was successfully destroyed AND items were dropped (or would have dropped if tool was right)
|
|
+ return destroyed && droppedItems;
|
|
+ } else {
|
|
+ // 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();
|
|
+ net.minecraft.world.level.block.Block block = state.getBlock();
|
|
+ net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item);
|
|
+ boolean result = false;
|
|
+
|
|
+ // Modelled off Player#hasCorrectToolForDrops
|
|
+ if (block != Blocks.AIR && (item == null || !state.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(state))) {
|
|
+ net.minecraft.world.level.block.Block.dropResources(state, this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), null, nmsItem, false); // Paper - Properly handle xp dropping
|
|
+ // Paper start - improve Block#breakNaturally
|
|
+ if (dropExperience)
|
|
+ block.popExperience(this.world.getMinecraftWorld(), this.position, block.getExpDrop(state, this.world.getMinecraftWorld(), this.position, nmsItem, true));
|
|
+ // Paper end
|
|
+ result = true;
|
|
+ }
|
|
+
|
|
+ if ((result && triggerEffect) || (forceEffect && block != Blocks.AIR)) {
|
|
+ if (state.getBlock() instanceof net.minecraft.world.level.block.BaseFireBlock) {
|
|
+ this.world.levelEvent(net.minecraft.world.level.block.LevelEvent.SOUND_EXTINGUISH_FIRE, this.position, 0);
|
|
+ } else {
|
|
+ this.world.levelEvent(net.minecraft.world.level.block.LevelEvent.PARTICLES_DESTROY_BLOCK, this.position, net.minecraft.world.level.block.Block.getId(state));
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // SPIGOT-6778: Directly call setBlock instead of setBlockState, so that the block entity is not removed and custom remove logic is run.
|
|
+ // Paper start - improve breakNaturally
|
|
+ boolean destroyed = this.world.removeBlock(this.position, false);
|
|
+ if (destroyed) {
|
|
+ block.destroy(this.world, this.position, state);
|
|
+ }
|
|
+ if (result) {
|
|
+ // special cases
|
|
+ if (block instanceof net.minecraft.world.level.block.IceBlock iceBlock) {
|
|
+ iceBlock.afterDestroy(this.world.getMinecraftWorld(), this.position, nmsItem);
|
|
+ } else if (block instanceof net.minecraft.world.level.block.TurtleEggBlock turtleEggBlock) {
|
|
+ turtleEggBlock.decreaseEggs(this.world.getMinecraftWorld(), this.position, state);
|
|
+ }
|
|
+ }
|
|
+ return destroyed && result;
|
|
+ // Paper end
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private boolean pwt$applyBoneMeal(BlockFace face) {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ServerLevel level = getServerLevel();
|
|
+ if (level != null) {
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, position, "PWT: Cannot modify world asynchronously (applyBoneMeal)");
|
|
+ } else {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Attempted applyBoneMeal on non-ServerLevel - safety not guaranteed.");
|
|
+ return false; // Cannot bonemeal without ServerLevel
|
|
+ }
|
|
+
|
|
+ // Original logic requires ServerLevel (level is guaranteed non-null here)
|
|
+ Direction direction = CraftBlock.blockFaceToNotch(face);
|
|
+ BlockFertilizeEvent event = null;
|
|
+ UseOnContext context = new UseOnContext(level, null, InteractionHand.MAIN_HAND, Items.BONE_MEAL.getDefaultInstance(), new BlockHitResult(Vec3.ZERO, direction, this.getPosition(), false));
|
|
+
|
|
+ // SPIGOT-6895: Call StructureGrowEvent and BlockFertilizeEvent
|
|
+ level.captureTreeGeneration = true;
|
|
+ InteractionResult result = BoneMealItem.applyBonemeal(context);
|
|
+ level.captureTreeGeneration = false;
|
|
+
|
|
+ if (!level.capturedBlockStates.isEmpty()) {
|
|
+ TreeType treeType = SaplingBlock.getTreeTypeRT(); // Use thread-local getter
|
|
+ SaplingBlock.setTreeTypeRT(null); // Use thread-local setter
|
|
+ // Need Bukkit BlockState list for events
|
|
+ List<org.bukkit.block.BlockState> bukkitStates = new ArrayList<>(level.capturedBlockStates.values());
|
|
+ level.capturedBlockStates.clear(); // Clear NMS map
|
|
+ StructureGrowEvent structureEvent = null;
|
|
+
|
|
+ if (treeType != null) {
|
|
+ structureEvent = new StructureGrowEvent(this.getLocation(), treeType, true, null, bukkitStates);
|
|
+ Bukkit.getPluginManager().callEvent(structureEvent);
|
|
+ }
|
|
+
|
|
+ event = new BlockFertilizeEvent(this, null, bukkitStates); // Use 'this' as the CraftBlock
|
|
+ event.setCancelled(structureEvent != null && structureEvent.isCancelled());
|
|
+ Bukkit.getPluginManager().callEvent(event);
|
|
+
|
|
+ if (!event.isCancelled()) {
|
|
+ for (BlockState state : bukkitStates) {
|
|
+ CraftBlockState craftBlockState = (CraftBlockState) state;
|
|
+ craftBlockState.place(craftBlockState.getFlags()); // This performs the actual block changes
|
|
+ // Notify observers using the captured state info
|
|
+ level.checkCapturedTreeStateForObserverNotify(this.position, craftBlockState);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Return true only if bonemeal succeeded AND the event wasn't cancelled
|
|
+ return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled());
|
|
+ } else {
|
|
+ Direction direction = CraftBlock.blockFaceToNotch(face);
|
|
+ BlockFertilizeEvent event = null;
|
|
+ ServerLevel world = this.getCraftWorld().getHandle();
|
|
+ UseOnContext context = new UseOnContext(world, null, InteractionHand.MAIN_HAND, Items.BONE_MEAL.getDefaultInstance(), new BlockHitResult(Vec3.ZERO, direction, this.getPosition(), false));
|
|
+
|
|
+ // SPIGOT-6895: Call StructureGrowEvent and BlockFertilizeEvent
|
|
+ world.captureTreeGeneration = true;
|
|
+ InteractionResult result = BoneMealItem.applyBonemeal(context);
|
|
+ world.captureTreeGeneration = false;
|
|
+
|
|
+ if (!world.capturedBlockStates.isEmpty()) {
|
|
+ TreeType treeType = SaplingBlock.treeType;
|
|
+ SaplingBlock.treeType = null;
|
|
+ List<BlockState> states = new ArrayList<>(world.capturedBlockStates.values());
|
|
+ world.capturedBlockStates.clear();
|
|
+ StructureGrowEvent structureEvent = null;
|
|
+
|
|
+ if (treeType != null) {
|
|
+ structureEvent = new StructureGrowEvent(this.getLocation(), treeType, true, null, states);
|
|
+ Bukkit.getPluginManager().callEvent(structureEvent);
|
|
+ }
|
|
+
|
|
+ event = new BlockFertilizeEvent(CraftBlock.at(world, this.getPosition()), null, states);
|
|
+ event.setCancelled(structureEvent != null && structureEvent.isCancelled());
|
|
+ Bukkit.getPluginManager().callEvent(event);
|
|
+
|
|
+ if (!event.isCancelled()) {
|
|
+ for (BlockState state : states) {
|
|
+ CraftBlockState craftBlockState = (CraftBlockState) state;
|
|
+ craftBlockState.place(craftBlockState.getFlags());
|
|
+ world.checkCapturedTreeStateForObserverNotify(this.position, craftBlockState); // Paper - notify observers even if grow failed
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private Collection<ItemStack> pwt$getDrops(ItemStack item, Entity entity) {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ // Requires ServerLevel for Block.getDrops call
|
|
+ ServerLevel level = getServerLevel();
|
|
+ if (level == null) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "PWT: Cannot getDrops: Not a ServerLevel.");
|
|
+ return Collections.emptyList();
|
|
+ }
|
|
+
|
|
+ // Strict check if applicable
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+ checkStrictMode(level, handlingMode, "getDrops");
|
|
+
|
|
+ try {
|
|
+ net.minecraft.world.level.block.state.BlockState state = this.getNMS(); // Use safe getNMS
|
|
+ net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(item);
|
|
+ net.minecraft.world.entity.Entity nmsEntity = (entity == null) ? null : ((CraftEntity) entity).getHandle();
|
|
+
|
|
+ // Check tool requirement
|
|
+ if (item == null || !state.requiresCorrectToolForDrops() || nmsItem.isCorrectToolForDrops(state)) {
|
|
+ // Call NMS getDrops using the verified ServerLevel
|
|
+ List<net.minecraft.world.item.ItemStack> nmsDrops = net.minecraft.world.level.block.Block.getDrops(
|
|
+ state, level, this.position, this.world.getBlockEntity(this.position), nmsEntity, nmsItem
|
|
+ );
|
|
+ // Convert to Bukkit ItemStacks
|
|
+ return nmsDrops.stream().map(CraftItemStack::asBukkitCopy).collect(Collectors.toList());
|
|
+ } else {
|
|
+ // Tool was required but not correct/present
|
|
+ return Collections.emptyList();
|
|
+ }
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getDrops", e);
|
|
+ return Collections.emptyList();
|
|
+ }
|
|
+ } else {
|
|
+ net.minecraft.world.level.block.state.BlockState state = this.getNMS();
|
|
+ net.minecraft.world.item.ItemStack nms = CraftItemStack.asNMSCopy(item);
|
|
+
|
|
+ // Modelled off Player#hasCorrectToolForDrops
|
|
+ if (item == null || CraftBlockData.isPreferredTool(state, nms)) {
|
|
+ return net.minecraft.world.level.block.Block.getDrops(state, this.world.getMinecraftWorld(), this.position, this.world.getBlockEntity(this.position), entity == null ? null : ((CraftEntity) entity).getHandle(), nms)
|
|
+ .stream().map(CraftItemStack::asBukkitCopy).collect(Collectors.toList());
|
|
+ } else {
|
|
+ return Collections.emptyList();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private RayTraceResult pwt$rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+
|
|
+ if (needsBuffering(level, handlingMode)) {
|
|
+ // Buffered path
|
|
+ HitResult nmsHitResult = executeBufferedRead(
|
|
+ level, org.dreeam.leaf.async.world.ReadOperationType.BLOCK_RAY_TRACE,
|
|
+ new Object[]{this.position, start, direction, maxDistance, fluidCollisionMode}, // Pass all params
|
|
+ null, "rayTrace"
|
|
+ );
|
|
+ // Convert NMS result (can be null) to Bukkit result
|
|
+ return CraftRayTraceResult.convertFromInternal(this.world, nmsHitResult);
|
|
+ } else {
|
|
+ // Strict/Disabled/Non-ServerLevel path
|
|
+ checkStrictMode(level, handlingMode, "rayTrace");
|
|
+ try {
|
|
+ // Calculate start and end points for the ray trace
|
|
+ Vec3 startPos = CraftLocation.toVec3(start);
|
|
+ Vector dirNormalized = direction.clone().normalize(); // Normalize once
|
|
+ Vec3 endPos = startPos.add(dirNormalized.getX() * maxDistance, dirNormalized.getY() * maxDistance, dirNormalized.getZ() * maxDistance);
|
|
+
|
|
+ // Perform the clip using LevelAccessor (safe)
|
|
+ // Pass the block's position as the context position for the clip function
|
|
+ HitResult nmsHitResult = this.world.clip(
|
|
+ new ClipContext(startPos, endPos, ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toFluid(fluidCollisionMode), CollisionContext.empty()),
|
|
+ this.position // Provide the block's position here
|
|
+ );
|
|
+
|
|
+ // Convert NMS result to Bukkit result
|
|
+ return CraftRayTraceResult.convertFromInternal(this.world, nmsHitResult);
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for rayTrace" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+ return null; // Return null on error
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ Vector dir = direction.clone().normalize().multiply(maxDistance);
|
|
+ Vec3 startPos = CraftLocation.toVec3(start);
|
|
+ Vec3 endPos = startPos.add(dir.getX(), dir.getY(), dir.getZ());
|
|
+
|
|
+ HitResult hitResult = this.world.clip(new ClipContext(startPos, endPos, ClipContext.Block.OUTLINE, CraftFluidCollisionMode.toFluid(fluidCollisionMode), CollisionContext.empty()), this.position);
|
|
+ return CraftRayTraceResult.convertFromInternal(this.world, hitResult);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private BoundingBox pwt$getBoundingBox() {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ // Requires LevelReader (LevelAccessor is sufficient)
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
+ checkStrictMode(level, handlingMode, "getBoundingBox"); // Strict check if applicable
|
|
+
|
|
+ try {
|
|
+ // Uses safe getNMS()
|
|
+ VoxelShape shape = this.getNMS().getShape(this.world, this.position);
|
|
+ if (shape.isEmpty()) {
|
|
+ // Return a zero-sized box at the block's corner if shape is empty
|
|
+ return new BoundingBox(getX(), getY(), getZ(), getX(), getY(), getZ());
|
|
+ }
|
|
+ // Get AABB relative to 0,0,0 and offset by block position
|
|
+ AABB aabb = shape.bounds();
|
|
+ return new BoundingBox(
|
|
+ this.getX() + aabb.minX, this.getY() + aabb.minY, this.getZ() + aabb.minZ,
|
|
+ this.getX() + aabb.maxX, this.getY() + aabb.maxY, this.getZ() + aabb.maxZ
|
|
+ );
|
|
+ } catch (Exception e) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "PWT: Direct access failed for getBoundingBox" + (level == null ? " (Not a ServerLevel)" : ""), e);
|
|
+ // Default to a full 1x1x1 box on error
|
|
+ return new BoundingBox(getX(), getY(), getZ(), getX() + 1, getY() + 1, getZ() + 1);
|
|
+ }
|
|
+ } else {
|
|
+ VoxelShape shape = this.getNMS().getShape(this.world, this.position);
|
|
+
|
|
+ if (shape.isEmpty()) {
|
|
+ return new BoundingBox(); // Return an empty bounding box if the block has no dimension
|
|
+ }
|
|
+
|
|
+ AABB aabb = shape.bounds();
|
|
+ return new BoundingBox(this.getX() + aabb.minX, this.getY() + aabb.minY, this.getZ() + aabb.minZ, this.getX() + aabb.maxX, this.getY() + aabb.maxY, this.getZ() + aabb.maxZ);
|
|
+ }
|
|
+ }
|
|
}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
|
|
index 5d4faad9df4824cfd61abfd4df011c006f114424..55308342f93d843796aa6a7d331846302558d087 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
|
|
@@ -33,6 +33,33 @@ public abstract class CraftBlockEntityState<T extends BlockEntity> extends Craft
|
|
private final T snapshot;
|
|
public boolean snapshotDisabled; // Paper
|
|
public static boolean DISABLE_SNAPSHOT = false; // Paper
|
|
+ private static final ThreadLocal<Boolean> DISABLE_SNAPSHOT_TL = ThreadLocal.withInitial(() -> Boolean.FALSE); // Leaf - SparklyPaper - parallel world ticking
|
|
+
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ // refer to original field in case plugins attempt to modify it
|
|
+ public static boolean getDisableSnapshotTL() {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ if (DISABLE_SNAPSHOT_TL.get()) return true;
|
|
+ synchronized (CraftBlockEntityState.class) {
|
|
+ return DISABLE_SNAPSHOT;
|
|
+ }
|
|
+ } else {
|
|
+ return DISABLE_SNAPSHOT;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // update original field in case plugins attempt to access it
|
|
+ public static void setDisableSnapshotTL(boolean value) {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ DISABLE_SNAPSHOT_TL.set(value);
|
|
+ synchronized (CraftBlockEntityState.class) {
|
|
+ DISABLE_SNAPSHOT = value;
|
|
+ }
|
|
+ } else {
|
|
+ DISABLE_SNAPSHOT = value;
|
|
+ }
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
|
|
public CraftBlockEntityState(World world, T blockEntity) {
|
|
super(world, blockEntity.getBlockPos(), blockEntity.getBlockState());
|
|
@@ -41,8 +68,8 @@ public abstract class CraftBlockEntityState<T extends BlockEntity> extends Craft
|
|
|
|
try { // Paper - Show blockstate location if we failed to read it
|
|
// Paper start
|
|
- this.snapshotDisabled = DISABLE_SNAPSHOT;
|
|
- if (DISABLE_SNAPSHOT) {
|
|
+ this.snapshotDisabled = getDisableSnapshotTL(); // Leaf - SparklyPaper - parallel world ticking
|
|
+ if (this.snapshotDisabled) { // Leaf - 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..7c40462a484776dfbff983a3636dec41479125e9 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();
|
|
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && access instanceof net.minecraft.server.level.ServerLevel serverLevel) {
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverLevel, position, "Cannot modify world asynchronously");
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - 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<org.bukkit.inventory.ItemStack> getDrops(org.bukkit.inventory.ItemStack item, org.bukkit.entity.Entity entity) {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(world.getHandle(), position, "Cannot modify world asynchronously"); // Leaf - 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 18f09de5c6549df3562e710ede825f75d69c046e..80ee9fdd9e3dd163801ada06ab4d8b8d062c8b0c 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.getDisableSnapshotTL(); // Leaf - SparklyPaper - parallel world ticking
|
|
+ CraftBlockEntityState.setDisableSnapshotTL(!useSnapshot); // Leaf - 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.setDisableSnapshotTL(prev); // Leaf - 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 b4ee0f809c1524c74eca74ee6bc471a3051d92a6..e9b7291f7a52bf80c8149eb038fae3f32c7f4983 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
|
|
@@ -829,6 +829,34 @@ public class CraftEventFactory {
|
|
}
|
|
|
|
public static BlockPos sourceBlockOverride = null; // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPos up to five methods deep.
|
|
+ private static final ThreadLocal<BlockPos> SOURCE_BLOCK_OVERRIDE_RT = new ThreadLocal<>(); // Leaf - SparklyPaper - parallel world ticking (this is from Folia, fixes concurrency bugs with sculk catalysts)
|
|
+
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ // refer to original field in case plugins attempt to modify it
|
|
+ public static BlockPos getSourceBlockOverrideRT() {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ final BlockPos sourceBlockOverrideRTCopy = SOURCE_BLOCK_OVERRIDE_RT.get();
|
|
+ if (sourceBlockOverrideRTCopy != null) return sourceBlockOverrideRTCopy;
|
|
+ synchronized (CraftEventFactory.class) {
|
|
+ return sourceBlockOverride;
|
|
+ }
|
|
+ } else {
|
|
+ return sourceBlockOverride;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // update original field in case plugins attempt to access it
|
|
+ public static void setSourceBlockOverrideRT(BlockPos value) {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ SOURCE_BLOCK_OVERRIDE_RT.set(value);
|
|
+ synchronized (CraftEventFactory.class) {
|
|
+ sourceBlockOverride = value;
|
|
+ }
|
|
+ } else {
|
|
+ sourceBlockOverride = value;
|
|
+ }
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
|
|
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);
|
|
@@ -844,7 +872,10 @@ 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);
|
|
+ // Leaf start - SparklyPaper parallel world ticking
|
|
+ final BlockPos sourceBlockOverrideRTSnap = getSourceBlockOverrideRT();
|
|
+ BlockSpreadEvent event = new BlockSpreadEvent(snapshot.getBlock(), CraftBlock.at(world, sourceBlockOverrideRTSnap != null ? sourceBlockOverrideRTSnap : source), snapshot);
|
|
+ // Leaf end - SparklyPaper parallel world ticking
|
|
if (event.callEvent()) {
|
|
boolean result = snapshot.place(flags);
|
|
return !checkSetResult || result;
|