9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-19 15:09:25 +00:00
Files
Leaf/leaf-server/paper-patches/features/0047-SparklyPaper-Parallel-world-ticking.patch
2025-09-09 11:59:03 -04:00

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;