mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-19 15:09:25 +00:00
1586 lines
94 KiB
Diff
1586 lines
94 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Altiami <yoshimo.kristin@gmail.com>
|
|
Date: Wed, 5 Mar 2025 13:16:44 -0800
|
|
Subject: [PATCH] SparklyPaper: Parallel world ticking
|
|
|
|
Original project: https://github.com/SparklyPower/SparklyPaper
|
|
|
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java
|
|
index 69cdd304d255d52c9b7dc9b6a33ffdb630b79abe..09dfab1ace05ee62df5cf6e292f8be0146e85a36 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.disableHardThrow; // SparklyPaper - parallel world ticking - THIS SHOULD NOT BE DISABLED SINCE IT CAN CAUSE DATA CORRUPTION!!! Anyhow, for production servers, if you want to make a test run to see if the server could crash, you can test it with this disabled
|
|
|
|
private static String getThreadContext() {
|
|
return "thread=" + Thread.currentThread().getName();
|
|
@@ -26,6 +27,7 @@ public class TickThread extends Thread {
|
|
public static void ensureTickThread(final String reason) {
|
|
if (!isTickThread()) {
|
|
LOGGER.error("Thread failed main thread check: " + reason + ", context=" + getThreadContext(), new Throwable());
|
|
+ if (HARD_THROW) // SparklyPaper - parallel world ticking
|
|
throw new IllegalStateException(reason);
|
|
}
|
|
}
|
|
@@ -33,8 +35,9 @@ public class TickThread extends Thread {
|
|
public static void ensureTickThread(final Level world, final BlockPos pos, final String reason) {
|
|
if (!isTickThreadFor(world, pos)) {
|
|
final String ex = "Thread failed main thread check: " +
|
|
- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos;
|
|
+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + " - " + getTickThreadInformation(world.getServer()); // SparklyPaper - parallel world ticking
|
|
LOGGER.error(ex, new Throwable());
|
|
+ if (HARD_THROW) // SparklyPaper - parallel world ticking
|
|
throw new IllegalStateException(ex);
|
|
}
|
|
}
|
|
@@ -42,8 +45,9 @@ public class TickThread extends Thread {
|
|
public static void ensureTickThread(final Level world, final BlockPos pos, final int blockRadius, final String reason) {
|
|
if (!isTickThreadFor(world, pos, blockRadius)) {
|
|
final String ex = "Thread failed main thread check: " +
|
|
- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + ", block_radius=" + blockRadius;
|
|
+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + ", block_radius=" + blockRadius + " - " + getTickThreadInformation(world.getServer()); // SparklyPaper - parallel world ticking
|
|
LOGGER.error(ex, new Throwable());
|
|
+ if (HARD_THROW) // SparklyPaper - parallel world ticking
|
|
throw new IllegalStateException(ex);
|
|
}
|
|
}
|
|
@@ -51,8 +55,9 @@ public class TickThread extends Thread {
|
|
public static void ensureTickThread(final Level world, final ChunkPos pos, final String reason) {
|
|
if (!isTickThreadFor(world, pos)) {
|
|
final String ex = "Thread failed main thread check: " +
|
|
- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + pos;
|
|
+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + pos + " - " + getTickThreadInformation(world.getServer()); // SparklyPaper - parallel world ticking
|
|
LOGGER.error(ex, new Throwable());
|
|
+ if (HARD_THROW) // SparklyPaper - parallel world ticking
|
|
throw new IllegalStateException(ex);
|
|
}
|
|
}
|
|
@@ -60,8 +65,9 @@ public class TickThread extends Thread {
|
|
public static void ensureTickThread(final Level world, final int chunkX, final int chunkZ, final String reason) {
|
|
if (!isTickThreadFor(world, chunkX, chunkZ)) {
|
|
final String ex = "Thread failed main thread check: " +
|
|
- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + new ChunkPos(chunkX, chunkZ);
|
|
+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + new ChunkPos(chunkX, chunkZ) + " - " + getTickThreadInformation(world.getServer()); // SparklyPaper - parallel world ticking
|
|
LOGGER.error(ex, new Throwable());
|
|
+ if (HARD_THROW) // SparklyPaper - parallel world ticking
|
|
throw new IllegalStateException(ex);
|
|
}
|
|
}
|
|
@@ -69,8 +75,9 @@ public class TickThread extends Thread {
|
|
public static void ensureTickThread(final Entity entity, final String reason) {
|
|
if (!isTickThreadFor(entity)) {
|
|
final String ex = "Thread failed main thread check: " +
|
|
- reason + ", context=" + getThreadContext() + ", entity=" + EntityUtil.dumpEntity(entity);
|
|
+ reason + ", context=" + getThreadContext() + ", entity=" + EntityUtil.dumpEntity(entity) + " - " + getTickThreadInformation(entity.getServer()); // SparklyPaper - parallel world ticking
|
|
LOGGER.error(ex, new Throwable());
|
|
+ if (HARD_THROW) // SparklyPaper - parallel world ticking
|
|
throw new IllegalStateException(ex);
|
|
}
|
|
}
|
|
@@ -78,8 +85,9 @@ public class TickThread extends Thread {
|
|
public static void ensureTickThread(final Level world, final AABB aabb, final String reason) {
|
|
if (!isTickThreadFor(world, aabb)) {
|
|
final String ex = "Thread failed main thread check: " +
|
|
- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb;
|
|
+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb + " - " + getTickThreadInformation(world.getServer()); // SparklyPaper - parallel world ticking
|
|
LOGGER.error(ex, new Throwable());
|
|
+ if (HARD_THROW) // SparklyPaper - parallel world ticking
|
|
throw new IllegalStateException(ex);
|
|
}
|
|
}
|
|
@@ -87,12 +95,71 @@ public class TickThread extends Thread {
|
|
public static void ensureTickThread(final Level world, final double blockX, final double blockZ, final String reason) {
|
|
if (!isTickThreadFor(world, blockX, blockZ)) {
|
|
final String ex = "Thread failed main thread check: " +
|
|
- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + new Vec3(blockX, 0.0, blockZ);
|
|
+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + new Vec3(blockX, 0.0, blockZ) + " - " + getTickThreadInformation(world.getServer()); // SparklyPaper - parallel world ticking
|
|
LOGGER.error(ex, new Throwable());
|
|
+ if (HARD_THROW) // SparklyPaper - parallel world ticking
|
|
throw new IllegalStateException(ex);
|
|
}
|
|
}
|
|
|
|
+ // SparklyPaper start - parallel world ticking
|
|
+ // This is an additional method to check if the tick thread is bound to a specific world because, by default, Paper's isTickThread methods do not provide this information
|
|
+ // Because we only tick worlds in parallel (instead of regions), we can use this for our checks
|
|
+ public static void ensureTickThread(final net.minecraft.server.level.ServerLevel world, final String reason) {
|
|
+ if (!isTickThreadFor(world)) {
|
|
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " - " + getTickThreadInformation(world.getServer()), new Throwable()); // SparklyPaper - parallel world ticking
|
|
+ if (HARD_THROW) throw new IllegalStateException(reason);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // This is an additional method to check if it is a tick thread but ONLY a tick thread
|
|
+ public static void ensureOnlyTickThread(final String reason) {
|
|
+ boolean isTickThread = isTickThread();
|
|
+ boolean isServerLevelTickThread = isServerLevelTickThread();
|
|
+ if (!isTickThread || isServerLevelTickThread) {
|
|
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread ONLY tick thread check: " + reason, new Throwable());
|
|
+ if (HARD_THROW) throw new IllegalStateException(reason);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // This is an additional method to check if the tick thread is bound to a specific world or if it is an async thread.
|
|
+ public static void ensureTickThreadOrAsyncThread(final net.minecraft.server.level.ServerLevel world, final String reason) {
|
|
+ boolean isValidTickThread = isTickThreadFor(world);
|
|
+ boolean isAsyncThread = !isTickThread();
|
|
+ boolean isValid = isAsyncThread || isValidTickThread;
|
|
+ if (!isValid) {
|
|
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread or async thread check: " + reason + " @ world " + world.getWorld().getName() + " - " + getTickThreadInformation(world.getServer()), new Throwable());
|
|
+ if (HARD_THROW) throw new IllegalStateException(reason);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static String getTickThreadInformation(net.minecraft.server.MinecraftServer minecraftServer) {
|
|
+ StringBuilder sb = new StringBuilder();
|
|
+ Thread currentThread = Thread.currentThread();
|
|
+ sb.append("Is tick thread? ");
|
|
+ sb.append(currentThread instanceof TickThread);
|
|
+ sb.append("; Is server level tick thread? ");
|
|
+ sb.append(currentThread instanceof ServerLevelTickThread);
|
|
+ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) {
|
|
+ sb.append("; Currently ticking level: ");
|
|
+ if (serverLevelTickThread.currentlyTickingServerLevel != null) {
|
|
+ sb.append(serverLevelTickThread.currentlyTickingServerLevel.getWorld().getName());
|
|
+ } else {
|
|
+ sb.append("null");
|
|
+ }
|
|
+ }
|
|
+ sb.append("; Is iterating over levels? ");
|
|
+ sb.append(minecraftServer.isIteratingOverLevels);
|
|
+ sb.append("; Are we going to hard throw? ");
|
|
+ sb.append(HARD_THROW);
|
|
+ return sb.toString();
|
|
+ }
|
|
+
|
|
+ public static boolean isServerLevelTickThread() {
|
|
+ return Thread.currentThread() instanceof ServerLevelTickThread;
|
|
+ }
|
|
+ // SparklyPaper end - parallel world ticking
|
|
+
|
|
public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */
|
|
|
|
private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
|
|
@@ -127,46 +194,74 @@ 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 mod (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 mod (add missing replacement / 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 mod (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 mod (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 mod (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 mod (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 mod (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 mod (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 mod (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 mod (use methods for what they were made for)
|
|
+ }
|
|
+
|
|
+ // SparklyPaper start - 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.currentlyTickingServerLevel == world;
|
|
+ } else {
|
|
+ return isTickThread();
|
|
+ }
|
|
}
|
|
|
|
public static boolean isTickThreadFor(final Entity entity) {
|
|
- return isTickThread();
|
|
+ if (entity == null) {
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ return isTickThreadFor(entity.level()); // Leaf - SparklyPaper - parallel world ticking mod (use methods for what they were made for)
|
|
+ }
|
|
+
|
|
+ public static class ServerLevelTickThread extends TickThread {
|
|
+ public ServerLevelTickThread(String name) {
|
|
+ super(name);
|
|
+ }
|
|
+
|
|
+ public ServerLevelTickThread(Runnable run, String name) {
|
|
+ super(run, name);
|
|
+ }
|
|
+
|
|
+ public net.minecraft.server.level.ServerLevel currentlyTickingServerLevel;
|
|
}
|
|
+ // SparklyPaper end - 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..ee5f342995a335593932a497c2bafd36d34cecb2 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
@@ -480,7 +480,12 @@ 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 mod (make configurable)
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled)
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot unload chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
|
+ else
|
|
+ org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot
|
|
+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable)
|
|
if (!this.isChunkLoaded(x, z)) {
|
|
return true;
|
|
}
|
|
@@ -497,6 +502,8 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|
|
|
@Override
|
|
public boolean refreshChunk(int x, int z) {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) // Leaf - SparklyPaper - parallel world ticking mod (make configurable)
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot refresh chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
|
ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
|
|
if (playerChunk == null) return false;
|
|
|
|
@@ -547,7 +554,12 @@ 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 mod (make configurable)
|
|
+ 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"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
|
+ else
|
|
+ org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot
|
|
+ // Leaf end - SparklyPaper - parallel world ticking mod (make configurable)
|
|
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 +787,8 @@ 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) // Leaf - SparklyPaper - parallel world ticking mod (make configurable)
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, loc.getX(), loc.getZ(), "Cannot generate tree asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
|
this.world.captureTreeGeneration = true;
|
|
this.world.captureBlockStates = true;
|
|
boolean grownTree = this.generateTree(loc, type);
|
|
@@ -890,6 +904,8 @@ 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) // Leaf - SparklyPaper - parallel world ticking mod (make configurable)
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot create explosion asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
|
net.minecraft.world.level.Level.ExplosionInteraction explosionType;
|
|
if (!breakBlocks) {
|
|
explosionType = net.minecraft.world.level.Level.ExplosionInteraction.NONE; // Don't break blocks
|
|
@@ -981,6 +997,8 @@ 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) // Leaf - SparklyPaper - parallel world ticking mod (make configurable)
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x >> 4, z >> 4, "Cannot retrieve chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
|
warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper
|
|
// Transient load for this tick
|
|
return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z);
|
|
@@ -1011,6 +1029,8 @@ 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) // Leaf - SparklyPaper - parallel world ticking mod (make configurable)
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, pos, "Cannot retrieve chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
|
if (this.world.hasChunkAt(pos)) {
|
|
net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkAt(pos);
|
|
|
|
@@ -2319,6 +2339,8 @@ 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) // Leaf - SparklyPaper - parallel world ticking mod (make configurable)
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, position.getX(), position.getZ(), "Cannot send game event asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
|
getHandle().gameEvent(sourceEntity != null ? ((CraftEntity) sourceEntity).getHandle(): null, net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.get(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(gameEvent.getKey())).orElseThrow(), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position));
|
|
}
|
|
// Paper end
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
|
|
index dd122bbbe2c33183017dbde6997d3f1cd08479b5..d164d50bcee7281283c6d9a7b85ee2596d89bcd1 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
|
|
@@ -74,12 +74,97 @@ 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;
|
|
+ }
|
|
+
|
|
+ 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
|
|
+ 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();
|
|
+ }
|
|
+ }
|
|
+ // 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
|
|
+ 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();
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
public BlockPos getPosition() {
|
|
@@ -142,10 +227,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 +242,18 @@ public class CraftBlock implements Block {
|
|
}
|
|
|
|
private void setData(final byte data, int flags) {
|
|
+ // SparklyPaper start - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable)
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously");
|
|
+ }
|
|
+ // SparklyPaper end - parallel world ticking
|
|
this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flags);
|
|
}
|
|
|
|
@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 +270,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 +290,11 @@ public class CraftBlock implements Block {
|
|
}
|
|
|
|
public static boolean setBlockState(LevelAccessor world, BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState, boolean applyPhysics) {
|
|
+ // SparklyPaper start - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable)
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, pos, "Cannot modify world asynchronously");
|
|
+ }
|
|
+ // SparklyPaper end - parallel world ticking
|
|
// SPIGOT-611: need to do this to prevent glitchiness. Easier to handle this here (like /setblock) than to fix weirdness in block entity cleanup
|
|
if (oldState.hasBlockEntity() && newState.getBlock() != oldState.getBlock()) { // SPIGOT-3725 remove old block entity if block changes
|
|
// SPIGOT-4612: faster - just clear tile
|
|
@@ -227,22 +326,62 @@ 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
|
|
+ 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;
|
|
+ }
|
|
+ // 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
|
|
+ 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;
|
|
+ }
|
|
+ // 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
|
|
+ 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;
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
public Block getFace(final BlockFace face) {
|
|
@@ -287,47 +426,32 @@ public class CraftBlock implements Block {
|
|
}
|
|
|
|
public static BlockFace notchToBlockFace(Direction notch) {
|
|
- if (notch == null) {
|
|
- return BlockFace.SELF;
|
|
- }
|
|
- switch (notch) {
|
|
- case DOWN:
|
|
- return BlockFace.DOWN;
|
|
- case UP:
|
|
- return BlockFace.UP;
|
|
- case NORTH:
|
|
- return BlockFace.NORTH;
|
|
- case SOUTH:
|
|
- return BlockFace.SOUTH;
|
|
- case WEST:
|
|
- return BlockFace.WEST;
|
|
- case EAST:
|
|
- return BlockFace.EAST;
|
|
- default:
|
|
- return BlockFace.SELF;
|
|
- }
|
|
+ // Leaf start - SparklyPaper - parallel world ticking - formatting
|
|
+ if (notch == null) return BlockFace.SELF;
|
|
+ return switch (notch) {
|
|
+ case DOWN -> BlockFace.DOWN;
|
|
+ case UP -> BlockFace.UP;
|
|
+ case NORTH -> BlockFace.NORTH;
|
|
+ case SOUTH -> BlockFace.SOUTH;
|
|
+ case WEST -> BlockFace.WEST;
|
|
+ case EAST -> BlockFace.EAST;
|
|
+ };
|
|
+ // Leaf end - SparklyPaper - parallel world ticking - formatting
|
|
}
|
|
|
|
public static Direction blockFaceToNotch(BlockFace face) {
|
|
- if (face == null) {
|
|
- return null;
|
|
- }
|
|
- switch (face) {
|
|
- case DOWN:
|
|
- return Direction.DOWN;
|
|
- case UP:
|
|
- return Direction.UP;
|
|
- case NORTH:
|
|
- return Direction.NORTH;
|
|
- case SOUTH:
|
|
- return Direction.SOUTH;
|
|
- case WEST:
|
|
- return Direction.WEST;
|
|
- case EAST:
|
|
- return Direction.EAST;
|
|
- default:
|
|
- return null;
|
|
- }
|
|
+ // Leaf start - SparklyPaper - parallel world ticking - formatting
|
|
+ if (face == null) return null;
|
|
+ return switch (face) {
|
|
+ case DOWN -> Direction.DOWN;
|
|
+ case UP -> Direction.UP;
|
|
+ case NORTH -> Direction.NORTH;
|
|
+ case SOUTH -> Direction.SOUTH;
|
|
+ case WEST -> Direction.WEST;
|
|
+ case EAST -> Direction.EAST;
|
|
+ default -> null;
|
|
+ };
|
|
+ // Leaf end - SparklyPaper - parallel world ticking - formatting
|
|
}
|
|
|
|
@Override
|
|
@@ -344,18 +468,65 @@ 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
|
|
+ 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
|
|
+ }
|
|
+ }
|
|
+ // 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
|
|
+ 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
|
|
+ }
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
// Paper end
|
|
|
|
@Override
|
|
public void setBiome(Biome bio) {
|
|
+ // SparklyPaper start - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && world instanceof ServerLevel serverWorld) { // Leaf - SparklyPaper - parallel world ticking mod (make configurable)
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously");
|
|
+ }
|
|
+ // SparklyPaper end - parallel world ticking
|
|
this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio);
|
|
}
|
|
|
|
@@ -371,12 +542,50 @@ public class CraftBlock implements Block {
|
|
|
|
@Override
|
|
public boolean isBlockPowered() {
|
|
- return this.world.getMinecraftWorld().getDirectSignalTo(this.position) > 0;
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ 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;
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
public boolean isBlockIndirectlyPowered() {
|
|
- return this.world.getMinecraftWorld().hasNeighborSignal(this.position);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ 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;
|
|
+ }
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
@@ -398,44 +607,103 @@ 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
|
|
+ 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;
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
public boolean isBlockFaceIndirectlyPowered(BlockFace face) {
|
|
- int power = this.world.getMinecraftWorld().getSignal(this.position, CraftBlock.blockFaceToNotch(face));
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ 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);
|
|
|
|
- Block relative = this.getRelative(face);
|
|
- if (relative.getType() == Material.REDSTONE_WIRE) {
|
|
- return Math.max(power, relative.getData()) > 0; // todo remove legacy usage
|
|
+ return false;
|
|
}
|
|
|
|
- return power > 0;
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
+ // Leaf start - 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) {
|
|
- if (!state.is(Blocks.REDSTONE_WIRE)) {
|
|
- return power;
|
|
+ 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;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Static helper, safe
|
|
+ public static int getPower(int currentMax, net.minecraft.world.level.block.state.BlockState neighborState) {
|
|
+ if (!neighborState.is(Blocks.REDSTONE_WIRE)) {
|
|
+ return currentMax;
|
|
} else {
|
|
- return Math.max(state.getValue(RedStoneWireBlock.POWER), power);
|
|
+ int neighborPower = neighborState.getValue(RedStoneWireBlock.POWER);
|
|
+ return Math.max(neighborPower, currentMax);
|
|
}
|
|
}
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
|
|
@Override
|
|
public int getBlockPower() {
|
|
@@ -478,23 +746,35 @@ 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
|
|
+ ServerLevel level = getServerLevel();
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ 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
|
|
+ // Leaf end - SparklyPaper - parallel world ticking - Write operation check
|
|
}
|
|
|
|
@Override
|
|
public boolean breakNaturally(ItemStack item) {
|
|
// Paper start
|
|
- return this.breakNaturally(item, false);
|
|
+ return this.breakNaturally(item, true, true); // 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
|
|
@@ -506,84 +786,147 @@ public class CraftBlock implements Block {
|
|
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
|
|
+ // Leaf start - SparklyPaper - parallel world ticking - Write operation check
|
|
+ // (already done in simpler overload, but check again for safety)
|
|
+ ServerLevel level = getServerLevel();
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ 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 result = false;
|
|
+ boolean droppedItems = 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;
|
|
- }
|
|
+ // Experience dropping requires ServerLevel
|
|
+ ServerLevel serverLevelForDrops = getServerLevel(); // Re-get ServerLevel specifically for drop logic
|
|
|
|
- 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));
|
|
+ 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);
|
|
}
|
|
|
|
- // 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
|
|
+ // 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);
|
|
}
|
|
- if (result) {
|
|
- // special cases
|
|
+ // 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(this.world.getMinecraftWorld(), this.position, nmsItem);
|
|
+ iceBlock.afterDestroy(nmsLevelForSpecialCases, this.position, nmsItem);
|
|
} else if (block instanceof net.minecraft.world.level.block.TurtleEggBlock turtleEggBlock) {
|
|
- turtleEggBlock.decreaseEggs(this.world.getMinecraftWorld(), this.position, state);
|
|
+ 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 destroyed && result;
|
|
- // Paper end
|
|
+ // Return true if the block was successfully destroyed AND items were dropped (or would have dropped if tool was right)
|
|
+ return destroyed && droppedItems;
|
|
+ // Leaf end - SparklyPaper - parallel world ticking - Write operation check
|
|
}
|
|
|
|
@Override
|
|
public boolean applyBoneMeal(BlockFace face) {
|
|
+ // Leaf start - SparklyPaper - parallel world ticking - Write operation check
|
|
+ ServerLevel level = getServerLevel();
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ 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
|
|
+ }
|
|
+ } else if (level == null) {
|
|
+ // If PWT is off, but it's still not a ServerLevel, we also can't bonemeal
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Cannot apply bonemeal: Not a ServerLevel.");
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ // Original logic requires ServerLevel (level is guaranteed non-null here)
|
|
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));
|
|
+ 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
|
|
- world.captureTreeGeneration = true;
|
|
+ level.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();
|
|
+ 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, states);
|
|
+ structureEvent = new StructureGrowEvent(this.getLocation(), treeType, true, null, bukkitStates);
|
|
Bukkit.getPluginManager().callEvent(structureEvent);
|
|
}
|
|
|
|
- event = new BlockFertilizeEvent(CraftBlock.at(world, this.getPosition()), null, states);
|
|
+ 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 : states) {
|
|
+ for (BlockState state : bukkitStates) {
|
|
CraftBlockState craftBlockState = (CraftBlockState) state;
|
|
- craftBlockState.place(craftBlockState.getFlags());
|
|
- world.checkCapturedTreeStateForObserverNotify(this.position, craftBlockState); // Paper - notify observers even if grow failed
|
|
+ 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());
|
|
+ // Leaf end - SparklyPaper - parallel world ticking - Write operation check
|
|
}
|
|
|
|
@Override
|
|
@@ -598,20 +941,45 @@ 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);
|
|
+ // Leaf start - SparklyPaper - parallel world ticking - Write operation check
|
|
+ // 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();
|
|
+ }
|
|
|
|
- // 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 {
|
|
+ // 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();
|
|
}
|
|
+ // Leaf end - 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 +988,23 @@ 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
|
|
+ // 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;
|
|
+ }
|
|
+ // 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,57 +1027,148 @@ public class CraftBlock implements Block {
|
|
|
|
@Override
|
|
public boolean isPassable() {
|
|
- return this.getNMS().getCollisionShape(this.world, this.position).isEmpty();
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ // 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.
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) {
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ // Validate inputs
|
|
Preconditions.checkArgument(start != null, "Location start cannot be null");
|
|
- Preconditions.checkArgument(this.getWorld().equals(start.getWorld()), "Location start cannot be a different world");
|
|
+ Preconditions.checkArgument(this.getWorld().equals(start.getWorld()), "Location start cannot be in a different world");
|
|
start.checkFinite();
|
|
|
|
Preconditions.checkArgument(direction != null, "Vector direction cannot be null");
|
|
direction.checkFinite();
|
|
- Preconditions.checkArgument(direction.lengthSquared() > 0, "Direction's magnitude (%s) must be greater than 0", direction.lengthSquared());
|
|
+ Preconditions.checkArgument(direction.lengthSquared() > 1.0E-8, "Direction's magnitude (%s) must be greater than 0", direction.lengthSquared()); // Avoid near-zero vectors
|
|
|
|
Preconditions.checkArgument(fluidCollisionMode != null, "FluidCollisionMode cannot be null");
|
|
- if (maxDistance < 0.0D) {
|
|
- return null;
|
|
- }
|
|
-
|
|
- Vector dir = direction.clone().normalize().multiply(maxDistance);
|
|
- Vec3 startPos = CraftLocation.toVec3(start);
|
|
- Vec3 endPos = startPos.add(dir.getX(), dir.getY(), dir.getZ());
|
|
+ if (maxDistance < 0.0D) return null; // Max distance must be non-negative
|
|
+
|
|
+ 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
|
|
+ );
|
|
|
|
- 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);
|
|
+ // 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
|
|
+ }
|
|
+ }
|
|
+ // Leaf end - 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
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ // 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);
|
|
}
|
|
|
|
- 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);
|
|
+ // Leaf end - 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
|
|
+ // 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
|
|
+ }
|
|
+ // 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
|
|
+ ServerLevel level = getServerLevel();
|
|
+ String handlingMode = org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.asyncUnsafeReadHandling;
|
|
|
|
- return iblockdata.canSurvive(world, this.position);
|
|
+ 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
|
|
+ }
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
@@ -711,7 +1184,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 = this.getNMS().getSoundType();
|
|
+ return new com.destroystokyo.paper.block.CraftBlockSoundGroup(nmsSoundType);
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@Override
|
|
@@ -724,26 +1200,76 @@ public class CraftBlock implements Block {
|
|
return this.getNMS().getBlock().getDescriptionId();
|
|
}
|
|
|
|
+ // Leaf start - SparklyPaper - parallel world ticking
|
|
+ @Override
|
|
public boolean isValidTool(ItemStack itemStack) {
|
|
- return getDrops(itemStack).size() != 0;
|
|
+ if (itemStack == null || itemStack.getType().isAir()) {
|
|
+ return false;
|
|
+ }
|
|
+ return !this.getDrops(itemStack).isEmpty();
|
|
+ // Leaf end - SparklyPaper - parallel world ticking
|
|
}
|
|
|
|
@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 = getServerLevel();
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ 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 if (level == null) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Cannot tick block: Not a ServerLevel.");
|
|
+ return;
|
|
+ }
|
|
+
|
|
this.getNMS().tick(level, this.position, level.random);
|
|
+ // Leaf end - SparklyPaper - parallel world ticking -
|
|
}
|
|
|
|
|
|
@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 = getServerLevel();
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ 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 if (level == null) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Cannot tick fluid: Not a ServerLevel.");
|
|
+ return;
|
|
+ }
|
|
+ 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 = getServerLevel();
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled) {
|
|
+ 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 if (level == null) {
|
|
+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Cannot randomTick block: Not a ServerLevel.");
|
|
+ return;
|
|
+ }
|
|
this.getNMS().randomTick(level, this.position, level.random);
|
|
+ // Leaf end - SparklyPaper - parallel world ticking - Write operation check
|
|
}
|
|
// Paper end
|
|
}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
|
|
index 5d4faad9df4824cfd61abfd4df011c006f114424..40fb6081bc2a6045c76f6e86584327758627f444 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
|
|
@@ -33,6 +33,27 @@ public abstract class CraftBlockEntityState<T extends BlockEntity> extends Craft
|
|
private final T snapshot;
|
|
public boolean snapshotDisabled; // Paper
|
|
public static boolean DISABLE_SNAPSHOT = false; // Paper
|
|
+ public static ThreadLocal<Boolean> DISABLE_SNAPSHOT_TL = ThreadLocal.withInitial(() -> Boolean.FALSE); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (distinguish name)
|
|
+
|
|
+ // Leaf start - SparklyPaper - parallel world ticking mod
|
|
+ // refer to original field in case plugins attempt to modify it
|
|
+ public static boolean getDisableSnapshotTL() {
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && DISABLE_SNAPSHOT_TL.get())
|
|
+ return true;
|
|
+ synchronized (CraftBlockEntityState.class) {
|
|
+ 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;
|
|
+ }
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking mod
|
|
|
|
public CraftBlockEntityState(World world, T blockEntity) {
|
|
super(world, blockEntity.getBlockPos(), blockEntity.getBlockState());
|
|
@@ -41,8 +62,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(); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior)
|
|
+ if (this.snapshotDisabled) { // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior)
|
|
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..eb7e63d4549e672ff1206055d2d754395f189a4a 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
|
|
@@ -218,6 +218,12 @@ public class CraftBlockState implements BlockState {
|
|
LevelAccessor access = this.getWorldHandle();
|
|
CraftBlock block = this.getBlock();
|
|
|
|
+ // SparklyPaper start - parallel world ticking
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && access instanceof net.minecraft.server.level.ServerLevel serverWorld) {
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously");
|
|
+ }
|
|
+ // SparklyPaper end - parallel world ticking
|
|
+
|
|
if (block.getType() != this.getType()) {
|
|
if (!force) {
|
|
return false;
|
|
@@ -365,6 +371,8 @@ 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) // Leaf - SparklyPaper - parallel world ticking mod (make configurable)
|
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(world.getHandle(), position, "Cannot modify world asynchronously"); // SparklyPaper - parallel world ticking
|
|
this.requirePlaced();
|
|
net.minecraft.world.item.ItemStack nms = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item);
|
|
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java
|
|
index 18f09de5c6549df3562e710ede825f75d69c046e..1b06f97caeda6f33938ff5391ecaad5a1fc26f36 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(); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior)
|
|
+ CraftBlockEntityState.setDisableSnapshotTL(!useSnapshot); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior)
|
|
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); // SparklyPaper - parallel world ticking // Leaf - SparklyPaper - parallel world ticking mod (collapse original behavior)
|
|
}
|
|
}
|
|
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
|
|
index b4ee0f809c1524c74eca74ee6bc471a3051d92a6..96177467807e75bacb8c7c11ba7263f89bc0933a 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
|
|
@@ -829,6 +829,28 @@ 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.
|
|
+ public static final ThreadLocal<BlockPos> sourceBlockOverrideRT = new ThreadLocal<>(); // SparklyPaper - parallel world ticking (this is from Folia, fixes concurrency bugs with sculk catalysts)
|
|
+
|
|
+ // Leaf start - SparklyPaper - parallel world ticking mod
|
|
+ // refer to original field in case plugins attempt to modify it
|
|
+ public static BlockPos getSourceBlockOverrideRT() {
|
|
+ BlockPos sourceBlockOverrideRTCopy;
|
|
+ if (org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking.enabled && (sourceBlockOverrideRTCopy = sourceBlockOverrideRT.get()) != null)
|
|
+ return sourceBlockOverrideRTCopy;
|
|
+ synchronized (CraftEventFactory.class) {
|
|
+ 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)
|
|
+ sourceBlockOverrideRT.set(value);
|
|
+ synchronized (CraftEventFactory.class) {
|
|
+ sourceBlockOverride = value;
|
|
+ }
|
|
+ }
|
|
+ // Leaf end - SparklyPaper - parallel world ticking mod
|
|
|
|
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 +866,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 mod (collapse original behavior)
|
|
+ final BlockPos sourceBlockOverrideRTSnap = getSourceBlockOverrideRT();
|
|
+ BlockSpreadEvent event = new BlockSpreadEvent(snapshot.getBlock(), CraftBlock.at(world, sourceBlockOverrideRTSnap != null ? sourceBlockOverrideRTSnap : source), snapshot); // SparklyPaper - parallel world ticking
|
|
+ // Leaf end - SparklyPaper parallel world ticking mod (collapse original behavior)
|
|
if (event.callEvent()) {
|
|
boolean result = snapshot.place(flags);
|
|
return !checkSetResult || result;
|