9
0
mirror of https://github.com/SparklyPower/SparklyPaper.git synced 2025-12-19 15:09:27 +00:00
Files
SparklyPaperMC/patches/server/0013-Parallel-world-ticking.patch
2023-11-20 23:07:10 -03:00

2178 lines
143 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrPowerGamerBR <git@mrpowergamerbr.com>
Date: Tue, 7 Nov 2023 01:34:14 -0300
Subject: [PATCH] Parallel world ticking
"mom can we have folia?" "we already have folia at home" folia at home:
diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
index abd0217cf0bff183c8e262edc173a53403797c1a..1ef797d2c743077c40c7e1796d4afe324e162970 100644
--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
@@ -1023,7 +1023,7 @@ public final class ChunkHolderManager {
if (changedFullStatus.isEmpty()) {
return;
}
- if (!TickThread.isTickThread()) {
+ if (!TickThread.isTickThreadFor(world)) { // SparklyPaper - parallel world ticking
this.taskScheduler.scheduleChunkTask(() -> {
final ArrayDeque<NewChunkHolder> pendingFullLoadUpdate = ChunkHolderManager.this.pendingFullLoadUpdate;
for (int i = 0, len = changedFullStatus.size(); i < len; ++i) {
@@ -1052,7 +1052,7 @@ public final class ChunkHolderManager {
// note: never call while inside the chunk system, this will absolutely break everything
public void processUnloads() {
- TickThread.ensureTickThread("Cannot unload chunks off-main");
+ TickThread.ensureTickThread(world, "Cannot unload chunks off-main"); // SparklyPaper - parallel world ticking
if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) {
throw new IllegalStateException("Cannot unload chunks recursively");
@@ -1327,14 +1327,14 @@ public final class ChunkHolderManager {
}
private boolean processTicketUpdates(final boolean checkLocks, final boolean processFullUpdates, List<ChunkProgressionTask> scheduledTasks) {
- TickThread.ensureTickThread("Cannot process ticket levels off-main");
+ TickThread.ensureTickThread(world, "Cannot process ticket levels off-main"); // SparklyPaper - parallel world ticking
if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) {
throw new IllegalStateException("Cannot update ticket level while unloading chunks or updating entity manager");
}
List<NewChunkHolder> changedFullStatus = null;
- final boolean isTickThread = TickThread.isTickThread();
+ final boolean isTickThread = TickThread.isTickThreadFor(world);
boolean ret = false;
final boolean canProcessFullUpdates = processFullUpdates & isTickThread;
diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java
index f975cb93716e137d973ff2f9011acdbef58859a2..cc510eea4b872e1238f97846db638b2a7028a66d 100644
--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java
+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java
@@ -327,7 +327,7 @@ public final class ChunkTaskScheduler {
public void scheduleTickingState(final int chunkX, final int chunkZ, final FullChunkStatus toStatus,
final boolean addTicket, final PrioritisedExecutor.Priority priority,
final Consumer<LevelChunk> onComplete) {
- if (!TickThread.isTickThread()) {
+ if (!TickThread.isTickThreadFor(world, chunkX, chunkZ)) { // SparklyPaper - parallel world ticking (other threads may call this method, such as the container stillValid check, which may trigger a chunk load in a different thread)
this.scheduleChunkTask(chunkX, chunkZ, () -> {
ChunkTaskScheduler.this.scheduleTickingState(chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
}, priority);
@@ -483,7 +483,7 @@ public final class ChunkTaskScheduler {
public void scheduleChunkLoad(final int chunkX, final int chunkZ, final ChunkStatus toStatus, final boolean addTicket,
final PrioritisedExecutor.Priority priority, final Consumer<ChunkAccess> onComplete) {
- if (!TickThread.isTickThread()) {
+ if (!TickThread.isTickThreadFor(world, chunkX, chunkZ)) { // SparklyPaper - parallel world ticking
this.scheduleChunkTask(chunkX, chunkZ, () -> {
ChunkTaskScheduler.this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
}, priority);
diff --git a/src/main/java/io/papermc/paper/util/TickThread.java b/src/main/java/io/papermc/paper/util/TickThread.java
index f9063e2282f89e97a378f06822cde0a64ab03f9a..98abe711f63c0a112ef969bd74bda81f2a72ed82 100644
--- a/src/main/java/io/papermc/paper/util/TickThread.java
+++ b/src/main/java/io/papermc/paper/util/TickThread.java
@@ -17,6 +17,7 @@ import java.util.concurrent.atomic.AtomicInteger;
public class TickThread extends Thread {
public static final boolean STRICT_THREAD_CHECKS = Boolean.getBoolean("paper.strict-thread-checks");
+ public static final boolean HARD_THROW = !Boolean.getBoolean("sparklypaper.disableHardThrow"); // SparklyPaper - parallel world ticking - THIS SHOULD NOT BE DISABLED SINCE IT CAN CAUSE DATA CORRUPTION!!! Anyhow, for production servers, if you want to make a test run to see if the server could crash, you can test it with this disabled
static {
if (STRICT_THREAD_CHECKS) {
@@ -41,50 +42,93 @@ public class TickThread extends Thread {
@Deprecated
public static void ensureTickThread(final String reason) {
if (!isTickThread()) {
- MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
- throw new IllegalStateException(reason);
+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " - " + getTickThreadInformation(), new Throwable());
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
}
}
public static void ensureTickThread(final ServerLevel world, final BlockPos pos, final String reason) {
if (!isTickThreadFor(world, pos)) {
- MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
- throw new IllegalStateException(reason);
+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " blockPos: " + pos + " - " + getTickThreadInformation(), new Throwable());
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
}
}
public static void ensureTickThread(final ServerLevel world, final ChunkPos pos, final String reason) {
if (!isTickThreadFor(world, pos)) {
- MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
- throw new IllegalStateException(reason);
+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " chunkPos: " + pos + " - " + getTickThreadInformation(), new Throwable());
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
}
}
public static void ensureTickThread(final ServerLevel world, final int chunkX, final int chunkZ, final String reason) {
if (!isTickThreadFor(world, chunkX, chunkZ)) {
- MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
- throw new IllegalStateException(reason);
+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " chunkX: " + chunkX + " chunkZ: " + chunkZ + " - " + getTickThreadInformation(), new Throwable());
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
}
}
public static void ensureTickThread(final Entity entity, final String reason) {
if (!isTickThreadFor(entity)) {
- MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
- throw new IllegalStateException(reason);
+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ entity " + entity.getStringUUID() + " - " + getTickThreadInformation(), new Throwable());
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
}
}
public static void ensureTickThread(final ServerLevel world, final AABB aabb, final String reason) {
if (!isTickThreadFor(world, aabb)) {
- MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
- throw new IllegalStateException(reason);
+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " aabb: " + aabb + " - " + getTickThreadInformation(), new Throwable());
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
}
}
public static void ensureTickThread(final ServerLevel world, final double blockX, final double blockZ, final String reason) {
if (!isTickThreadFor(world, blockX, blockZ)) {
- MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
- throw new IllegalStateException(reason);
+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " blockX: " + blockX + " blockZ: " + blockZ + " - " + getTickThreadInformation(), new Throwable());
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
+ }
+ }
+
+ // SparklyPaper - parallel world ticking
+ // This is an additional method to check if the tick thread is bound to a specific world 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 ServerLevel world, final String reason) {
+ if (!isTickThreadFor(world)) {
+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " - " + getTickThreadInformation(), new Throwable());
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
+ }
+ }
+
+ // SparklyPaper - parallel world ticking
+ // This is an additional method to check if it is a tick thread but ONLY a tick thread
+ public static void ensureOnlyTickThread(final String reason) {
+ boolean isTickThread = isTickThread();
+ boolean isServerLevelTickThread = isServerLevelTickThread();
+ if (!isTickThread || isServerLevelTickThread) {
+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread ONLY tick thread check: " + reason + " - " + getTickThreadInformation(), new Throwable());
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
+ }
+ }
+
+ // SparklyPaper - parallel world ticking
+ // This is an additional method to check if the tick thread is bound to a specific world or if it is an async thread.
+ public static void ensureTickThreadOrAsyncThread(final ServerLevel world, final String reason) {
+ boolean isValidTickThread = isTickThreadFor(world);
+ boolean isAsyncThread = !isTickThread();
+ boolean isValid = isAsyncThread || isValidTickThread;
+ if (!isValid) {
+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread or async thread check: " + reason + " @ world " + world.getWorld().getName() + " - " + getTickThreadInformation(), new Throwable());
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
}
}
@@ -109,6 +153,32 @@ public class TickThread extends Thread {
return (TickThread)Thread.currentThread();
}
+ public static String getTickThreadInformation() {
+ 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.getServer().isIteratingOverLevels);
+ sb.append("; Are we going to hard throw? ");
+ sb.append(HARD_THROW);
+ return sb.toString();
+ }
+
+ public static boolean isServerLevelTickThread() {
+ return Thread.currentThread() instanceof ServerLevelTickThread;
+ }
+
public static boolean isTickThread() {
return Thread.currentThread() instanceof TickThread;
}
@@ -118,42 +188,111 @@ public class TickThread extends Thread {
}
public static boolean isTickThreadFor(final ServerLevel world, final BlockPos pos) {
- return isTickThread();
+ Thread currentThread = Thread.currentThread();
+
+ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) {
+ return serverLevelTickThread.currentlyTickingServerLevel == world;
+ } else return currentThread instanceof TickThread;
}
public static boolean isTickThreadFor(final ServerLevel world, final ChunkPos pos) {
- return isTickThread();
+ Thread currentThread = Thread.currentThread();
+
+ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) {
+ return serverLevelTickThread.currentlyTickingServerLevel == world;
+ } else return currentThread instanceof TickThread;
}
public static boolean isTickThreadFor(final ServerLevel world, final Vec3 pos) {
- return isTickThread();
+ Thread currentThread = Thread.currentThread();
+
+ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) {
+ return serverLevelTickThread.currentlyTickingServerLevel == world;
+ } else return currentThread instanceof TickThread;
}
public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ) {
- return isTickThread();
+ Thread currentThread = Thread.currentThread();
+
+ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) {
+ return serverLevelTickThread.currentlyTickingServerLevel == world;
+ } else return currentThread instanceof TickThread;
}
public static boolean isTickThreadFor(final ServerLevel world, final AABB aabb) {
- return isTickThread();
+ Thread currentThread = Thread.currentThread();
+
+ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) {
+ return serverLevelTickThread.currentlyTickingServerLevel == world;
+ } else return currentThread instanceof TickThread;
}
public static boolean isTickThreadFor(final ServerLevel world, final double blockX, final double blockZ) {
- return isTickThread();
+ Thread currentThread = Thread.currentThread();
+
+ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) {
+ return serverLevelTickThread.currentlyTickingServerLevel == world;
+ } else return currentThread instanceof TickThread;
}
public static boolean isTickThreadFor(final ServerLevel world, final Vec3 position, final Vec3 deltaMovement, final int buffer) {
- return isTickThread();
+ Thread currentThread = Thread.currentThread();
+
+ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) {
+ return serverLevelTickThread.currentlyTickingServerLevel == world;
+ } else return currentThread instanceof TickThread;
}
public static boolean isTickThreadFor(final ServerLevel world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) {
- return isTickThread();
+ Thread currentThread = Thread.currentThread();
+
+ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) {
+ return serverLevelTickThread.currentlyTickingServerLevel == world;
+ } else return currentThread instanceof TickThread;
}
public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ, final int radius) {
- return isTickThread();
+ Thread currentThread = Thread.currentThread();
+
+ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) {
+ return serverLevelTickThread.currentlyTickingServerLevel == world;
+ } else return currentThread instanceof TickThread;
+ }
+
+ // SparklyPaper - parallel world ticking
+ // This is an additional method to check if the tick thread is bound to a specific world because, by default, Paper's isTickThread methods do not provide this information
+ // Because we only tick worlds in parallel (instead of regions), we can use this for our checks
+ public static boolean isTickThreadFor(final ServerLevel world) {
+ Thread currentThread = Thread.currentThread();
+
+ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) {
+ return serverLevelTickThread.currentlyTickingServerLevel == world;
+ } else return currentThread instanceof TickThread;
}
public static boolean isTickThreadFor(final Entity entity) {
- return isTickThread();
+ if (entity == null) {
+ return true;
+ }
+
+ Thread currentThread = Thread.currentThread();
+
+ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) {
+ return serverLevelTickThread.currentlyTickingServerLevel == entity.level();
+ } else return currentThread instanceof TickThread;
+ }
+
+ // SparklyPaper start - parallel world ticking
+ public static class ServerLevelTickThread extends TickThread {
+ public ServerLevelTickThread(String name) {
+ super(name);
+ }
+
+ public ServerLevelTickThread(Runnable run, String name) {
+ super(run, name);
+ }
+
+ public ServerLevel currentlyTickingServerLevel;
}
+ // SparklyPaper end
}
diff --git a/src/main/java/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java b/src/main/java/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java
index 155bd3d6d9c7d3cac7fd04de8210301251d1e17a..f51d90f54ae693e7e9c8aa0ea14af9fdd26351f9 100644
--- a/src/main/java/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java
+++ b/src/main/java/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java
@@ -32,7 +32,7 @@ public abstract class AbstractProjectileDispenseBehavior extends DefaultDispense
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) enumdirection.getStepX(), (double) ((float) enumdirection.getStepY() + 0.1F), (double) enumdirection.getStepZ()));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
worldserver.getCraftServer().getPluginManager().callEvent(event);
}
diff --git a/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java
index 80dbeb0a988c749feaaba26ce5ad93c181d88a5d..4e08ae0af6e357347486d5c994f7dd413236c2b1 100644
--- a/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java
+++ b/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java
@@ -62,7 +62,7 @@ public class BoatDispenseItemBehavior extends DefaultDispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d1, d2 + d4, d3));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
worldserver.getCraftServer().getPluginManager().callEvent(event);
}
diff --git a/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java
index 379890ae05b2fb4bd81b2fa907413d3736ba1169..e9a21ce76cca0f6e615a447451048df9cf6b637e 100644
--- a/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java
+++ b/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java
@@ -74,7 +74,7 @@ public class DefaultDispenseItemBehavior implements DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), CraftVector.toBukkit(entityitem.getDeltaMovement()));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
world.getCraftServer().getPluginManager().callEvent(event);
}
diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
index a0c7c6208314d981e8577ad69ef1c5193290a085..5be8039092cd6a2a787fdfaf9d516a18a60f3d53 100644
--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
@@ -222,7 +222,7 @@ public interface DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
worldserver.getCraftServer().getPluginManager().callEvent(event);
}
@@ -277,7 +277,7 @@ public interface DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
worldserver.getCraftServer().getPluginManager().callEvent(event);
}
@@ -333,7 +333,7 @@ public interface DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) list.get(0).getBukkitEntity());
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
world.getCraftServer().getPluginManager().callEvent(event);
}
@@ -389,7 +389,7 @@ public interface DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) entityhorseabstract.getBukkitEntity());
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
world.getCraftServer().getPluginManager().callEvent(event);
}
@@ -463,7 +463,7 @@ public interface DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) entityhorsechestedabstract.getBukkitEntity());
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
world.getCraftServer().getPluginManager().callEvent(event);
}
@@ -502,7 +502,7 @@ public interface DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(enumdirection.getStepX(), enumdirection.getStepY(), enumdirection.getStepZ()));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
worldserver.getCraftServer().getPluginManager().callEvent(event);
}
@@ -560,7 +560,7 @@ public interface DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d3, d4, d5));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
worldserver.getCraftServer().getPluginManager().callEvent(event);
}
@@ -633,7 +633,7 @@ public interface DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(x, y, z));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
worldserver.getCraftServer().getPluginManager().callEvent(event);
}
@@ -707,7 +707,7 @@ public interface DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event
BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
worldserver.getCraftServer().getPluginManager().callEvent(event);
}
@@ -754,7 +754,7 @@ public interface DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack);
BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
worldserver.getCraftServer().getPluginManager().callEvent(event);
}
@@ -815,7 +815,7 @@ public interface DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
worldserver.getCraftServer().getPluginManager().callEvent(event);
}
@@ -844,8 +844,8 @@ public interface DispenseItemBehavior {
// CraftBukkit start
worldserver.captureTreeGeneration = false;
if (worldserver.capturedBlockStates.size() > 0) {
- TreeType treeType = SaplingBlock.treeType;
- SaplingBlock.treeType = null;
+ TreeType treeType = SaplingBlock.treeTypeRT.get(); // SparklyPaper - parallel world ticking
+ SaplingBlock.treeTypeRT.set(null); // SparklyPaper - parallel world ticking
Location location = CraftLocation.toBukkit(blockposition, worldserver.getWorld());
List<org.bukkit.block.BlockState> blocks = new java.util.ArrayList<>(worldserver.capturedBlockStates.values());
worldserver.capturedBlockStates.clear();
@@ -884,7 +884,7 @@ public interface DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
worldserver.getCraftServer().getPluginManager().callEvent(event);
}
@@ -941,7 +941,7 @@ public interface DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event
BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
worldserver.getCraftServer().getPluginManager().callEvent(event);
}
@@ -990,7 +990,7 @@ public interface DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event
BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
worldserver.getCraftServer().getPluginManager().callEvent(event);
}
@@ -1063,7 +1063,7 @@ public interface DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - only single item in event
BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
worldserver.getCraftServer().getPluginManager().callEvent(event);
}
diff --git a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
index e17090003988ad2c890d48666c2234b14d511345..6b82ce7423fe08238bd9b44b442ca9b05a3101d8 100644
--- a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
+++ b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
@@ -40,7 +40,7 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack);
BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
worldserver.getCraftServer().getPluginManager().callEvent(event);
}
diff --git a/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java
index 6f2adf2334e35e8a617a4ced0c1af2abf32bbd8d..a5ea9df0a021ed820c0c1ccb612caebd582878e2 100644
--- a/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java
+++ b/src/main/java/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java
@@ -37,7 +37,7 @@ public class ShulkerBoxDispenseBehavior extends OptionalDispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event
BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
pointer.level().getCraftServer().getPluginManager().callEvent(event);
}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index a308b52c26c2bb1bfee9f50e03480676b53e602f..e48cb1a99f6876393eaaa98eeca41c0a5eb6b52c 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -160,6 +160,7 @@ import net.minecraft.world.level.storage.PrimaryLevelData;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.level.storage.WorldData;
import net.minecraft.world.level.storage.loot.LootDataManager;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
import org.slf4j.Logger;
// CraftBukkit start
@@ -308,6 +309,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// Paper start - lag compensation
public static final long SERVER_INIT = System.nanoTime();
// Paper end - lag compensation
+ // SparklyPaper - parallel world ticking
+ public java.util.concurrent.Semaphore serverLevelTickingSemaphore = null;
+ // SparklyPaper end
public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
AtomicReference<S> atomicreference = new AtomicReference();
@@ -1523,63 +1527,124 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.isIteratingOverLevels = true; // Paper
Iterator iterator = this.getAllLevels().iterator(); // Paper - move down
- while (iterator.hasNext()) {
- ServerLevel worldserver = (ServerLevel) iterator.next();
- worldserver.updateLagCompensationTick(); // Paper - lag compensation
- worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
- net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper
- worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
+ // SparklyPaper start - parallel world ticking
+ java.util.ArrayDeque<java.util.concurrent.Future<ServerLevel>> tasks = new java.util.ArrayDeque<>();
+ // while (iterator.hasNext()) { // SparklyPaper - commented out to cause diff when upstream changes this code
+ // ServerLevel worldserver = (ServerLevel) iterator.next();
+ // worldserver.updateLagCompensationTick(); // Paper - lag compensation
+ // worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
+ // net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper
+ // worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
+ //
+ // this.profiler.push(() -> {
+ // return worldserver + " " + worldserver.dimension().location();
+ // });
+ // /* Drop global time updates
+ // if (this.tickCount % 20 == 0) {
+ // this.profiler.push("timeSync");
+ // this.synchronizeTime(worldserver);
+ // this.profiler.pop();
+ // }
+ // // CraftBukkit end */
+ //
+ // this.profiler.push("tick");
+ //
+ // try {
+ // worldserver.timings.doTick.startTiming(); // Spigot
+ // long i = Util.getNanos(); // SparklyPaper - track world's MSPT
+ // worldserver.tick(shouldKeepTicking);
+ // // SparklyPaper start - track world's MSPT
+ // long j = this.tickTimes[this.tickCount % 100] = Util.getNanos() - i;
+ //
+ // // These are from the "tickServer" function
+ // worldserver.tickTimes5s.add(this.tickCount, j);
+ // worldserver.tickTimes10s.add(this.tickCount, j);
+ // worldserver.tickTimes60s.add(this.tickCount, j);
+ // // SparklyPaper end
+ // // Paper start
+ // for (final io.papermc.paper.chunk.SingleThreadChunkRegionManager regionManager : worldserver.getChunkSource().chunkMap.regionManagers) {
+ // regionManager.recalculateRegions();
+ // }
+ // // Paper end
+ // worldserver.timings.doTick.stopTiming(); // Spigot
+ // } catch (Throwable throwable) {
+ // // Spigot Start
+ // CrashReport crashreport;
+ // try {
+ // crashreport = CrashReport.forThrowable(throwable, "Exception ticking world");
+ // } catch (Throwable t) {
+ // if (throwable instanceof ThreadDeath) { throw (ThreadDeath)throwable; } // Paper
+ // throw new RuntimeException("Error generating crash report", t);
+ // }
+ // // Spigot End
+ //
+ // worldserver.fillReportDetails(crashreport);
+ // throw new ReportedException(crashreport);
+ // }
+ //
+ // this.profiler.pop();
+ // this.profiler.pop();
+ // worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions
+ // }
+ try {
+ while (iterator.hasNext()) {
+ ServerLevel worldserver = (ServerLevel) iterator.next();
+ worldserver.updateLagCompensationTick(); // Paper - lag compensation
+ worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
+ net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper
+ worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
+
+ serverLevelTickingSemaphore.acquire();
+ tasks.add(
+ worldserver.tickExecutor.submit(() -> {
+ try {
+ io.papermc.paper.util.TickThread.ServerLevelTickThread currentThread = (io.papermc.paper.util.TickThread.ServerLevelTickThread) Thread.currentThread();
+ currentThread.currentlyTickingServerLevel = worldserver;
+
+ long i = Util.getNanos(); // SparklyPaper - track world's MSPT
+ worldserver.tick(shouldKeepTicking);
+ for (final io.papermc.paper.chunk.SingleThreadChunkRegionManager regionManager : worldserver.getChunkSource().chunkMap.regionManagers) {
+ regionManager.recalculateRegions();
+ }
+ worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions
- this.profiler.push(() -> {
- return worldserver + " " + worldserver.dimension().location();
- });
- /* Drop global time updates
- if (this.tickCount % 20 == 0) {
- this.profiler.push("timeSync");
- this.synchronizeTime(worldserver);
- this.profiler.pop();
- }
- // CraftBukkit end */
+ // SparklyPaper start - track world's MSPT
+ long j = Util.getNanos() - i;
- this.profiler.push("tick");
+ // These are from the "tickServer" function
+ worldserver.tickTimes5s.add(this.tickCount, j);
+ worldserver.tickTimes10s.add(this.tickCount, j);
+ worldserver.tickTimes60s.add(this.tickCount, j);
+ // SparklyPaper end
- try {
- worldserver.timings.doTick.startTiming(); // Spigot
- long i = Util.getNanos(); // SparklyPaper - track world's MSPT
- worldserver.tick(shouldKeepTicking);
- // SparklyPaper start - track world's MSPT
- long j = Util.getNanos() - i;
-
- // These are from the "tickServer" function
- worldserver.tickTimes5s.add(this.tickCount, j);
- worldserver.tickTimes10s.add(this.tickCount, j);
- worldserver.tickTimes60s.add(this.tickCount, j);
- // SparklyPaper end
- // Paper start
- for (final io.papermc.paper.chunk.SingleThreadChunkRegionManager regionManager : worldserver.getChunkSource().chunkMap.regionManagers) {
- regionManager.recalculateRegions();
- }
- // Paper end
- worldserver.timings.doTick.stopTiming(); // Spigot
- } catch (Throwable throwable) {
- // Spigot Start
- CrashReport crashreport;
- try {
- crashreport = CrashReport.forThrowable(throwable, "Exception ticking world");
- } catch (Throwable t) {
- if (throwable instanceof ThreadDeath) { throw (ThreadDeath)throwable; } // Paper
- throw new RuntimeException("Error generating crash report", t);
- }
- // Spigot End
+ currentThread.currentlyTickingServerLevel = null; // Reset current ticking level
+ } catch (Throwable throwable) {
+ // Spigot Start
+ CrashReport crashreport;
+ try {
+ crashreport = CrashReport.forThrowable(throwable, "Exception ticking world");
+ } catch (Throwable t) {
+ if (throwable instanceof ThreadDeath) { throw (ThreadDeath)throwable; } // Paper
+ throw new RuntimeException("Error generating crash report", t);
+ }
+ // Spigot End
- worldserver.fillReportDetails(crashreport);
- throw new ReportedException(crashreport);
+ worldserver.fillReportDetails(crashreport);
+ throw new ReportedException(crashreport);
+ } finally {
+ serverLevelTickingSemaphore.release();
+ }
+ }, worldserver)
+ );
}
- this.profiler.pop();
- this.profiler.pop();
- worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions
+ while (!tasks.isEmpty()) {
+ tasks.pop().get();
+ }
+ } catch (java.lang.InterruptedException | java.util.concurrent.ExecutionException e) {
+ throw new RuntimeException(e); // Propagate exception
}
+ // SparklyPaper end
this.isIteratingOverLevels = false; // Paper
this.profiler.popPush("connection");
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index 8c20221736419bcb9c3e570af624eef8e6fc3b09..52f7b6cc6f410e353ec8a5836d23d47c5c934675 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -17,6 +17,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
+import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import javax.annotation.Nullable;
import net.minecraft.DefaultUncaughtExceptionHandler;
@@ -50,6 +51,7 @@ import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.block.entity.SkullBlockEntity;
import net.minecraft.world.level.storage.LevelStorageSource;
+import net.sparklypower.sparklypaper.configs.SparklyPaperConfigUtils;
import org.slf4j.Logger;
// CraftBukkit start
@@ -226,6 +228,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
return false;
}
net.sparklypower.sparklypaper.SparklyPaperCommands.INSTANCE.registerCommands(this);
+ serverLevelTickingSemaphore = new java.util.concurrent.Semaphore(net.sparklypower.sparklypaper.configs.SparklyPaperConfigUtils.INSTANCE.getConfig().getParallelWorldTicking().getThreads()); // SparklyPaper - parallel world ticking
+ DedicatedServer.LOGGER.info("Using " + serverLevelTickingSemaphore.availablePermits() + " permits for parallel world ticking"); // SparklyPaper - parallel world ticking
// SparklyPaper end
com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now
io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // init PaperBrigadierProvider
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 8c33a12ca879c46893150d6adfb8aa4d397c6b4c..7088c8e8a7eba566fa91f5fa2995cd724705b8c4 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -238,7 +238,7 @@ public class ServerChunkCache extends ChunkSource {
public LevelChunk getChunkAtIfLoadedImmediately(int x, int z) {
long k = ChunkPos.asLong(x, z);
- if (io.papermc.paper.util.TickThread.isTickThread()) { // Paper - rewrite chunk system
+ if (io.papermc.paper.util.TickThread.isTickThreadFor(level, x, z)) { // Paper - rewrite chunk system // SparklyPaper - parallel world ticking
return this.getChunkAtIfLoadedMainThread(x, z);
}
@@ -265,7 +265,16 @@ public class ServerChunkCache extends ChunkSource {
@Override
public ChunkAccess getChunk(int x, int z, ChunkStatus leastStatus, boolean create) {
final int x1 = x; final int z1 = z; // Paper - conflict on variable change
- if (!io.papermc.paper.util.TickThread.isTickThread()) { // Paper - rewrite chunk system
+ if (!io.papermc.paper.util.TickThread.isTickThreadFor(level, x, z)) { // Paper - rewrite chunk system // SparklyPaper - parallel world ticking
+ // SparklyPaper start - parallel world ticking
+ if (io.papermc.paper.util.TickThread.isServerLevelTickThread()) {
+ // Welp, we are going to crash, bye
+ net.minecraft.server.MinecraftServer.LOGGER.error("THE SERVER IS GOING TO CRASH! - Thread " + Thread.currentThread().getName() + " failed main thread check: Cannot query another world's (" + level.getWorld().getName() + ") chunk (" + x + ", " + z + ") in a ServerLevelTickThread - " + io.papermc.paper.util.TickThread.getTickThreadInformation(), new Throwable());
+ if (io.papermc.paper.util.TickThread.HARD_THROW)
+ throw new IllegalStateException("Cannot query another world's (" + level.getWorld().getName() + ") chunk (" + x + ", " + z + ") in a ServerLevelTickThread");
+ }
+ // SparklyPaper end
+
return (ChunkAccess) CompletableFuture.supplyAsync(() -> {
return this.getChunk(x, z, leastStatus, create);
}, this.mainThreadProcessor).join();
@@ -317,7 +326,7 @@ public class ServerChunkCache extends ChunkSource {
@Nullable
@Override
public LevelChunk getChunkNow(int chunkX, int chunkZ) {
- if (!io.papermc.paper.util.TickThread.isTickThread()) { // Paper - rewrite chunk system
+ if (!io.papermc.paper.util.TickThread.isTickThreadFor(level, chunkX, chunkZ)) { // Paper - rewrite chunk system // SparklyPaper - parallel world ticking
return null;
} else {
return this.getChunkAtIfLoadedMainThread(chunkX, chunkZ); // Paper - optimise for loaded chunks
@@ -331,7 +340,7 @@ public class ServerChunkCache extends ChunkSource {
}
public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getChunkFuture(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) {
- boolean flag1 = io.papermc.paper.util.TickThread.isTickThread(); // Paper - rewrite chunk system
+ boolean flag1 = io.papermc.paper.util.TickThread.isTickThreadFor(level, chunkX, chunkZ); // Paper - rewrite chunk system // SparklyPaper - parallel world ticking
CompletableFuture completablefuture;
if (flag1) {
@@ -650,7 +659,7 @@ public class ServerChunkCache extends ChunkSource {
if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - optimise chunk tick iteration
this.level.tickChunk(chunk1, k);
- if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper
+ // if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper
}
}
}
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 3fe14f5f135249ea9004589a86ed372aeb4667f8..3e3f92cb5b740badf78133b0a942794860ddd1bb 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -216,6 +216,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
private final boolean tickTime;
private final RandomSequences randomSequences;
public long lastMidTickExecuteFailure; // Paper - execute chunk tasks mid tick
+ public java.util.concurrent.ExecutorService tickExecutor; // SparklyPaper - parallel world ticking
// CraftBukkit start
public final LevelStorageSource.LevelStorageAccess convertable;
@@ -703,7 +704,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile());
// CraftBukkit end
this.players = Lists.newArrayList();
- this.entityTickList = new EntityTickList();
+ this.entityTickList = new EntityTickList(this);
this.blockTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded, this.getProfilerSupplier());
this.fluidTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded, this.getProfilerSupplier());
this.navigatingMobs = new ObjectOpenHashSet();
@@ -774,6 +775,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
this.chunkTaskScheduler = new io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler(this, io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.workerThreads); // Paper - rewrite chunk system
this.entityLookup = new io.papermc.paper.chunk.system.entity.EntityLookup(this, new EntityCallbacks()); // Paper - rewrite chunk system
+ this.tickExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new net.sparklypower.sparklypaper.ServerLevelTickExecutorThreadFactory(getWorld().getName())); // SparklyPaper - parallel world ticking
}
// Paper start
@@ -1329,7 +1331,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
if (fluid1.is(fluid)) {
fluid1.tick(this, pos);
}
- MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick
+ // MinecraftServer.getServer().executeMidTickTasks(); // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper - exec chunk tasks during world tick
}
@@ -1339,7 +1341,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
if (iblockdata.is(block)) {
iblockdata.tick(this, pos, this.random);
}
- MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick
+ // MinecraftServer.getServer().executeMidTickTasks(); // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper - exec chunk tasks during world tick
}
@@ -1357,7 +1359,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
public void tickNonPassenger(Entity entity) {
// Paper start - log detailed entity tick information
- io.papermc.paper.util.TickThread.ensureTickThread("Cannot tick an entity off-main");
+ io.papermc.paper.util.TickThread.ensureTickThread(entity, "Cannot tick an entity off-main"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
try {
if (currentlyTickingEntity.get() == null) {
currentlyTickingEntity.lazySet(entity);
@@ -1645,6 +1647,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
}
private void addPlayer(ServerPlayer player) {
+ io.papermc.paper.util.TickThread.ensureTickThread(this, "Cannot add player off-main"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
Entity entity = (Entity) this.getEntities().get(player.getUUID());
if (entity != null) {
@@ -1658,7 +1661,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
// CraftBukkit start
private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) {
- org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot
+ io.papermc.paper.util.TickThread.ensureTickThread(this, "Cannot add entity off-main"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
// Paper start
if (entity.valid) {
MinecraftServer.LOGGER.error("Attempted Double World add on " + entity, new Throwable());
@@ -2632,6 +2635,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
@Override
public void close() throws IOException {
super.close();
+ tickExecutor.shutdown(); // SparklyPaper - parallel world ticking
//this.entityManager.close(); // Paper - rewrite chunk system
}
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index f71a4a8307fb092d33545e12d253e0b80c884168..e44f7734e677226ff8715134c81edad9520b5694 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -18,6 +18,7 @@ import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
import javax.annotation.Nullable;
+
import net.minecraft.BlockUtil;
import net.minecraft.ChatFormatting;
import net.minecraft.CrashReport;
@@ -95,7 +96,6 @@ import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.util.Unit;
import net.minecraft.world.damagesource.DamageSource;
-import net.minecraft.world.damagesource.DamageSources;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
@@ -114,12 +114,7 @@ import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.AbstractArrow;
import net.minecraft.world.food.FoodData;
-import net.minecraft.world.inventory.AbstractContainerMenu;
-import net.minecraft.world.inventory.ContainerListener;
-import net.minecraft.world.inventory.ContainerSynchronizer;
-import net.minecraft.world.inventory.HorseInventoryMenu;
-import net.minecraft.world.inventory.ResultSlot;
-import net.minecraft.world.inventory.Slot;
+import net.minecraft.world.inventory.*;
import net.minecraft.world.item.ComplexItem;
import net.minecraft.world.item.ItemCooldowns;
import net.minecraft.world.item.ItemStack;
@@ -321,6 +316,7 @@ public class ServerPlayer extends Player {
// Paper start - optimise chunk tick iteration
public double lastEntitySpawnRadiusSquared = -1.0;
// Paper end - optimise chunk tick iteration
+ public boolean hasTickedAtLeastOnceInNewWorld = false; // SparklyPaper - parallel world ticking (fixes bug in DreamResourceReset where the inventory is opened AFTER the player has changed worlds, if you click with the quick tp torch in a chest, because the inventory is opened AFTER the player has teleported)
public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) {
super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile);
@@ -710,6 +706,7 @@ public class ServerPlayer extends Player {
@Override
public void tick() {
+ hasTickedAtLeastOnceInNewWorld = true; // SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch)
// CraftBukkit start
if (this.joining) {
this.joining = false;
@@ -728,6 +725,7 @@ public class ServerPlayer extends Player {
containerUpdateDelay = this.level().paperConfig().tickRates.containerUpdate;
}
// Paper end
+ // SparklyPaper - parallel world ticking (debugging)
if (!this.level().isClientSide && this.containerMenu != this.inventoryMenu && (this.isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - auto close while frozen
this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper
this.containerMenu = this.inventoryMenu;
@@ -1182,6 +1180,7 @@ public class ServerPlayer extends Player {
ResourceKey<LevelStem> resourcekey = worldserver1.getTypeKey(); // CraftBukkit
if (resourcekey == LevelStem.END && worldserver != null && worldserver.getTypeKey() == LevelStem.OVERWORLD) { // CraftBukkit
+ io.papermc.paper.util.TickThread.ensureOnlyTickThread("Cannot change dimension of a player off-main, from world " + serverLevel().getWorld().getName() + " to world " + worldserver.getWorld().getName()); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
this.isChangingDimension = true; // CraftBukkit - Moved down from above
this.unRide();
this.serverLevel().removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
@@ -1194,6 +1193,10 @@ public class ServerPlayer extends Player {
return this;
} else {
+ if (worldserver != null)
+ io.papermc.paper.util.TickThread.ensureOnlyTickThread("Cannot change dimension of a player off-main, from world " + serverLevel().getWorld().getName() + " to world " + worldserver.getWorld().getName()); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
+ else
+ io.papermc.paper.util.TickThread.ensureOnlyTickThread("Cannot change dimension of a player off-main, from world " + serverLevel().getWorld().getName() + " to unknown world"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
// CraftBukkit start
/*
WorldData worlddata = worldserver.getLevelData();
@@ -1594,6 +1597,12 @@ public class ServerPlayer extends Player {
return OptionalInt.empty();
} else {
// CraftBukkit start
+ // SparklyPaper start - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch)
+ if (!hasTickedAtLeastOnceInNewWorld) {
+ MinecraftServer.LOGGER.warn("Ignoring request to open container " + container + " because we haven't ticked in the current world yet!", new Throwable());
+ return OptionalInt.empty();
+ }
+ // SparklyPaper end
this.containerMenu = container;
if (!this.isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), Objects.requireNonNullElseGet(title, container::getTitle))); // Paper
// CraftBukkit end
@@ -1655,6 +1664,7 @@ public class ServerPlayer extends Player {
}
@Override
public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
+ MinecraftServer.LOGGER.warn("Closing " + this.getBukkitEntity().getName() + " inventory that was created at", this.containerMenu.containerCreationStacktrace);
CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit
// Paper end
this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId));
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index 33abcf12b4426572b74ca4c813e4392c823494bc..07198f2f8f7cb082c9e575a5c1e56c14aca31a87 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -111,7 +111,6 @@ import org.bukkit.Location;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.entity.CraftPlayer;
-import org.bukkit.craftbukkit.util.CraftChatMessage;
import org.bukkit.craftbukkit.util.CraftLocation;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerChangedWorldEvent;
@@ -120,7 +119,6 @@ import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerRespawnEvent.RespawnReason;
-import org.bukkit.event.player.PlayerSpawnChangeEvent;
// CraftBukkit end
public abstract class PlayerList {
@@ -136,7 +134,7 @@ public abstract class PlayerList {
private static final SimpleDateFormat BAN_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
private final MinecraftServer server;
public final List<ServerPlayer> players = new java.util.concurrent.CopyOnWriteArrayList(); // CraftBukkit - ArrayList -> CopyOnWriteArrayList: Iterator safety
- private final Map<UUID, ServerPlayer> playersByUUID = Maps.newHashMap();
+ private final Map<UUID, ServerPlayer> playersByUUID = Maps.newHashMap(); // SparklyPaper - parallel world ticking (we don't need to replace the original map because we never iterate on top of this map)
private final UserBanList bans;
private final IpBanList ipBans;
private final ServerOpList ops;
@@ -157,7 +155,7 @@ public abstract class PlayerList {
// CraftBukkit start
private CraftServer cserver;
- private final Map<String,ServerPlayer> playersByName = new java.util.HashMap<>();
+ private final Map<String,ServerPlayer> playersByName = new java.util.HashMap<>(); // SparklyPaper - parallel world ticking (we don't need to replace the original map because we never iterate on top of this map)
public @Nullable String collideRuleTeamName; // Paper - Team name used for collideRule
public PlayerList(MinecraftServer server, LayeredRegistryAccess<RegistryLayer> registryManager, PlayerDataStorage saveHandler, int maxPlayers) {
@@ -181,6 +179,7 @@ public abstract class PlayerList {
abstract public void loadAndSaveFiles(); // Paper - moved from DedicatedPlayerList constructor
public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) {
+ io.papermc.paper.util.TickThread.ensureOnlyTickThread("Cannot place new player off-main"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
player.isRealPlayer = true; // Paper
player.loginTime = System.currentTimeMillis(); // Paper
GameProfile gameprofile = player.getGameProfile();
@@ -820,6 +819,8 @@ public abstract class PlayerList {
}
public ServerPlayer respawn(ServerPlayer entityplayer, ServerLevel worldserver, boolean flag, Location location, boolean avoidSuffocation, RespawnReason reason, org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag...respawnFlags) {
+ System.out.println("respawning player - current player container is " + entityplayer.containerMenu + " but their inventory is " + entityplayer.inventoryMenu);
+ io.papermc.paper.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, from world " + entityplayer.serverLevel().getWorld().getName() + " to world " + worldserver.getWorld().getName()); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
// Paper end
entityplayer.stopRiding(); // CraftBukkit
this.players.remove(entityplayer);
@@ -844,6 +845,7 @@ public abstract class PlayerList {
ServerPlayer entityplayer1 = entityplayer;
org.bukkit.World fromWorld = entityplayer.getBukkitEntity().getWorld();
entityplayer.wonGame = false;
+ entityplayer.hasTickedAtLeastOnceInNewWorld = false; // SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch)
// CraftBukkit end
entityplayer1.connection = entityplayer.connection;
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index e544081e8214802facb77defc1e9aa765834be2a..f979d22f5bf83492133a87119686a4e136923bc0 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -934,11 +934,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
// This will be called every single tick the entity is in lava, so don't throw an event
this.setSecondsOnFire(15, false);
}
- CraftEventFactory.blockDamage = (this.lastLavaContact) == null ? null : org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.lastLavaContact);
+ CraftEventFactory.blockDamageRT.set((this.lastLavaContact) == null ? null : org.bukkit.craftbukkit.block.CraftBlock.at(this.level, this.lastLavaContact)); // SparklyPaper - parallel world ticking
if (this.hurt(this.damageSources().lava(), 4.0F)) {
this.playSound(SoundEvents.GENERIC_BURN, 0.4F, 2.0F + this.random.nextFloat() * 0.4F);
}
- CraftEventFactory.blockDamage = null;
+ CraftEventFactory.blockDamageRT.set(null); // SparklyPaper - parallel world ticking
// CraftBukkit end - we also don't throw an event unless the object in lava is living, to save on some event calls
}
@@ -3390,9 +3390,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
if (this.fireImmune()) {
return;
}
- CraftEventFactory.entityDamage = lightning;
+ CraftEventFactory.entityDamageRT.set(lightning); // SparklyPaper - parallel world ticking
if (!this.hurt(this.damageSources().lightningBolt(), 5.0F)) {
- CraftEventFactory.entityDamage = null;
+ CraftEventFactory.entityDamageRT.set(null); // SparklyPaper - parallel world ticking
return;
}
// CraftBukkit end
@@ -3903,6 +3903,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
this.teleportPassengers();
this.setYHeadRot(yaw);
} else {
+ io.papermc.paper.util.TickThread.ensureTickThread(world, "Cannot teleport entity to another world off-main, from world " + level.getWorld().getName() + " to world " + world.getWorld().getName()); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
this.unRide();
Entity entity = this.getType().create(world);
diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
index 490472bb618e9ac07da5883a71dff8920525b1e2..73e4d849d151ec3adec80a96c1e6efbe35b5044a 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
@@ -341,9 +341,9 @@ public class Turtle extends Animal {
@Override
public void thunderHit(ServerLevel world, LightningBolt lightning) {
- org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = lightning; // CraftBukkit
+ org.bukkit.craftbukkit.event.CraftEventFactory.entityDamageRT.set(lightning); // CraftBukkit // SparklyPaper - parallel world ticking
this.hurt(this.damageSources().lightningBolt(), Float.MAX_VALUE);
- org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = null; // CraftBukkit
+ org.bukkit.craftbukkit.event.CraftEventFactory.entityDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking
}
@Override
diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
index e6f75a9cac46c8e3ddba664a9d5b27b665a94cb4..07df7ce695452409b4ac132d8fb72bed2415b08e 100644
--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -295,9 +295,9 @@ public class FallingBlockEntity extends Entity {
float f2 = (float) Math.min(Mth.floor((float) i * this.fallDamagePerDistance), this.fallDamageMax);
this.level().getEntities((Entity) this, this.getBoundingBox(), predicate).forEach((entity) -> {
- CraftEventFactory.entityDamage = this; // CraftBukkit
+ CraftEventFactory.entityDamageRT.set(this); // CraftBukkit // SparklyPaper - parallel world ticking
entity.hurt(damagesource2, f2);
- CraftEventFactory.entityDamage = null; // CraftBukkit
+ CraftEventFactory.entityDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking
});
boolean flag = this.blockState.is(BlockTags.ANVIL);
diff --git a/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java b/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java
index bbdb82b319480b103df463cce3c1b8e3dd5857ec..46093cd5d70ad6a95b2407aa5d749a3790ccd92a 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java
@@ -129,9 +129,9 @@ public class EvokerFangs extends Entity implements TraceableEntity {
if (target.isAlive() && !target.isInvulnerable() && target != entityliving1) {
if (entityliving1 == null) {
- org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = this; // CraftBukkit
+ org.bukkit.craftbukkit.event.CraftEventFactory.entityDamageRT.set(this); // CraftBukkit // SparklyPaper - parallel world ticking
target.hurt(this.damageSources().magic(), 6.0F);
- org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = null; // CraftBukkit
+ org.bukkit.craftbukkit.event.CraftEventFactory.entityDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking
} else {
if (entityliving1.isAlliedTo((Entity) target)) {
return;
diff --git a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
index b2f08889139dc447f7071f1c81456035bf8de31e..f816f197df8f36c83d5fe5b6d677da91bac2c16f 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
@@ -240,9 +240,9 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier {
if (f > 0.0F) {
if (this.attachedToEntity != null) {
- CraftEventFactory.entityDamage = this; // CraftBukkit
+ CraftEventFactory.entityDamageRT.set(this); // CraftBukkit // SparklyPaper - parallel world ticking
this.attachedToEntity.hurt(this.damageSources().fireworks(this, this.getOwner()), 5.0F + (float) (nbttaglist.size() * 2));
- CraftEventFactory.entityDamage = null; // CraftBukkit
+ CraftEventFactory.entityDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking
}
double d0 = 5.0D;
@@ -269,9 +269,9 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier {
if (flag) {
float f1 = f * (float) Math.sqrt((5.0D - (double) this.distanceTo(entityliving)) / 5.0D);
- CraftEventFactory.entityDamage = this; // CraftBukkit
+ CraftEventFactory.entityDamageRT.set(this); // CraftBukkit // SparklyPaper - parallel world ticking
entityliving.hurt(this.damageSources().fireworks(this, this.getOwner()), f1);
- CraftEventFactory.entityDamage = null; // CraftBukkit
+ CraftEventFactory.entityDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking
}
}
}
diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
index a90317100d32974e481e14476843f66997a2cf3a..981a73abdef08bf4a2de274abbccff35415f3a95 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
@@ -78,16 +78,19 @@ public abstract class Projectile extends Entity implements TraceableEntity {
} else if (this.ownerUUID != null && this.level() instanceof ServerLevel) {
this.cachedOwner = ((ServerLevel) this.level()).getEntity(this.ownerUUID);
// Paper start - check all worlds
- if (this.cachedOwner == null) {
- for (final ServerLevel level : this.level().getServer().getAllLevels()) {
- if (level == this.level()) continue;
- final Entity entity = level.getEntity(this.ownerUUID);
- if (entity != null) {
- this.cachedOwner = entity;
- break;
- }
- }
- }
+ // SparklyPaper start - parallel world ticking
+ // This is from the "MC-50319: Check other worlds for shooter of projectiles" patch, however, accessing the entities in other worlds will cause concurrency issues
+ // if (this.cachedOwner == null) {
+ // for (final ServerLevel level : this.level().getServer().getAllLevels()) {
+ // if (level == this.level()) continue;
+ // final Entity entity = level.getEntity(this.ownerUUID);
+ // if (entity != null) {
+ // this.cachedOwner = entity;
+ // break;
+ // }
+ // }
+ // }
+ // SparklyPaper end
// Paper end
this.refreshProjectileSource(false); // Paper
return this.cachedOwner;
diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
index f5db60cbecbe69941873e064315931089fe0e48a..6c4a1de4f2606439348dbdb620a1aff63b848028 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
@@ -82,9 +82,9 @@ public class ThrownEnderpearl extends ThrowableItemProjectile {
entityplayer.connection.teleport(teleEvent.getTo());
entity.resetFallDistance();
- CraftEventFactory.entityDamage = this;
+ CraftEventFactory.entityDamageRT.set(this); // SparklyPaper - parallel world ticking
entity.hurt(this.damageSources().fall(), 5.0F);
- CraftEventFactory.entityDamage = null;
+ CraftEventFactory.entityDamageRT.set(null); // SparklyPaper - parallel world ticking
}
// CraftBukkit end
}
diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java
index f664da5a8413bb13cc95d2cf1604f11a5d285dae..42da66ca4fbb37cac1d8db20f8f4f1c83f8f5fd7 100644
--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java
+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java
@@ -102,6 +102,7 @@ public abstract class AbstractContainerMenu {
this.title = title;
}
// CraftBukkit end
+ public Throwable containerCreationStacktrace = new Throwable(); // SparklyPaper - parallel world ticking (debugging)
protected AbstractContainerMenu(@Nullable MenuType<?> type, int syncId) {
this.carried = ItemStack.EMPTY;
diff --git a/src/main/java/net/minecraft/world/item/ArmorItem.java b/src/main/java/net/minecraft/world/item/ArmorItem.java
index 42d87800a328f71c5127ce5599ca4c71cc9bb1cd..466526dfe8f81379bccf640f2c3a70640c353540 100644
--- a/src/main/java/net/minecraft/world/item/ArmorItem.java
+++ b/src/main/java/net/minecraft/world/item/ArmorItem.java
@@ -69,7 +69,7 @@ public class ArmorItem extends Item implements Equipable {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) entityliving.getBukkitEntity());
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
world.getCraftServer().getPluginManager().callEvent(event);
}
diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java
index 4697df75fdee2023c41260bed211e3e3d90d2b9b..618a0a4d479c73fd0ca7e9d770ea01deb10c004a 100644
--- a/src/main/java/net/minecraft/world/item/ItemStack.java
+++ b/src/main/java/net/minecraft/world/item/ItemStack.java
@@ -378,8 +378,8 @@ public final class ItemStack {
if (enuminteractionresult.consumesAction() && world.captureTreeGeneration && world.capturedBlockStates.size() > 0) {
world.captureTreeGeneration = false;
Location location = CraftLocation.toBukkit(blockposition, world.getWorld());
- TreeType treeType = SaplingBlock.treeType;
- SaplingBlock.treeType = null;
+ TreeType treeType = SaplingBlock.treeTypeRT.get(); // SparklyPaper - parallel world ticking
+ SaplingBlock.treeTypeRT.set(null); // SparklyPaper - parallel world ticking
List<CraftBlockState> blocks = new java.util.ArrayList<>(world.capturedBlockStates.values());
world.capturedBlockStates.clear();
StructureGrowEvent structureEvent = null;
diff --git a/src/main/java/net/minecraft/world/item/MinecartItem.java b/src/main/java/net/minecraft/world/item/MinecartItem.java
index a33395dc5a94d89b5ab273c7832813b6ff9ea3b7..31abddcd78672814de8d1e6289da67782675081a 100644
--- a/src/main/java/net/minecraft/world/item/MinecartItem.java
+++ b/src/main/java/net/minecraft/world/item/MinecartItem.java
@@ -69,7 +69,7 @@ public class MinecartItem extends Item {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
BlockDispenseEvent event = new BlockDispenseEvent(block2, craftItem.clone(), new org.bukkit.util.Vector(d0, d1 + d3, d2));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
worldserver.getCraftServer().getPluginManager().callEvent(event);
}
diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java
index 45243249a561440512ef2a620c60b02e159c80e2..849b9b4336d2ac99324dacf6ad8a2e34e7e65797 100644
--- a/src/main/java/net/minecraft/world/level/Explosion.java
+++ b/src/main/java/net/minecraft/world/level/Explosion.java
@@ -566,7 +566,7 @@ public class Explosion {
continue;
}
- CraftEventFactory.entityDamage = this.source;
+ CraftEventFactory.entityDamageRT.set(this.source); // SparklyPaper - parallel world ticking
entity.lastDamageCancelled = false;
if (entity instanceof EnderDragon) {
@@ -582,7 +582,7 @@ public class Explosion {
entity.hurt(this.getDamageSource(), (float) ((int) ((d13 * d13 + d13) / 2.0D * 7.0D * (double) f2 + 1.0D)));
}
- CraftEventFactory.entityDamage = null;
+ CraftEventFactory.entityDamageRT.set(null); // SparklyPaper - parallel world ticking
if (entity.lastDamageCancelled) { // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Skip entity if damage event was cancelled
continue;
}
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index b9e0822638a3979bd43392efdb595153e6f34675..28caabc36e7af64d3f546fc0e3c9c06bc5c8b78d 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -15,6 +15,8 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javax.annotation.Nullable;
+
+import io.papermc.paper.util.TickThread;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
@@ -827,7 +829,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline
// Paper start - make sure loaded chunks get the inlined variant of this function
net.minecraft.server.level.ServerChunkCache cps = ((ServerLevel)this).getChunkSource();
- if (cps.mainThread == Thread.currentThread()) {
+ if (TickThread.isTickThreadFor((ServerLevel) this, chunkX, chunkZ)) { // SparklyPaper - parallel world ticking, let tick threads load chunks via the main thread
LevelChunk ifLoaded = cps.getChunkAtIfLoadedMainThread(chunkX, chunkZ);
if (ifLoaded != null) {
return ifLoaded;
@@ -911,6 +913,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
@Override
public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) {
+ io.papermc.paper.util.TickThread.ensureTickThread((ServerLevel)this, pos, "Updating block asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
// CraftBukkit start - tree generation
if (this.captureTreeGeneration) {
// Paper start
@@ -1292,7 +1295,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
tickingblockentity.tick();
// Paper start - execute chunk tasks during tick
if ((this.tileTickPosition & 7) == 0) {
- MinecraftServer.getServer().executeMidTickTasks();
+ // MinecraftServer.getServer().executeMidTickTasks(); // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks)
}
// Paper end - execute chunk tasks during tick
}
@@ -1309,7 +1312,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
public <T extends Entity> void guardEntityTick(Consumer<T> tickConsumer, T entity) {
try {
tickConsumer.accept(entity);
- MinecraftServer.getServer().executeMidTickTasks(); // Paper - execute chunk tasks mid tick
+ // MinecraftServer.getServer().executeMidTickTasks(); // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper - execute chunk tasks mid tick
} catch (Throwable throwable) {
if (throwable instanceof ThreadDeath) throw throwable; // Paper
// Paper start - Prevent tile entity and entity crashes
@@ -1409,6 +1412,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
@Nullable
public BlockEntity getBlockEntity(BlockPos blockposition, boolean validate) {
+ io.papermc.paper.util.TickThread.ensureTickThreadOrAsyncThread((ServerLevel) this, "Cannot read world asynchronously"); // SparklyPaper start - parallel world ticking
// Paper start - Optimize capturedTileEntities lookup
net.minecraft.world.level.block.entity.BlockEntity blockEntity;
if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(blockposition)) != null) {
@@ -1416,10 +1420,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
}
// Paper end
// CraftBukkit end
- return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && !io.papermc.paper.util.TickThread.isTickThread() ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE)); // Paper - rewrite chunk system
+ return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && !io.papermc.paper.util.TickThread.isTickThread() ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EntityCreationType.IMMEDIATE)); // Paper - rewrite chunk system // SparklyPaper - parallel world ticking
}
public void setBlockEntity(BlockEntity blockEntity) {
+ io.papermc.paper.util.TickThread.ensureTickThread((ServerLevel) this, "Cannot modify world asynchronously"); // SparklyPaper start - parallel world ticking
BlockPos blockposition = blockEntity.getBlockPos();
if (!this.isOutsideBuildHeight(blockposition)) {
@@ -1505,6 +1510,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
@Override
public List<Entity> getEntities(@Nullable Entity except, AABB box, Predicate<? super Entity> predicate) {
+ io.papermc.paper.util.TickThread.ensureTickThread((ServerLevel)this, box, "Cannot getEntities asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
this.getProfiler().incrementCounter("getEntities");
List<Entity> list = Lists.newArrayList();
((ServerLevel)this).getEntityLookup().getEntities(except, box, list, predicate); // Paper - optimise this call
@@ -1769,8 +1775,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
}
public final BlockPos.MutableBlockPos getRandomBlockPosition(int x, int y, int z, int l, BlockPos.MutableBlockPos out) {
// Paper end
- this.randValue = this.randValue * 3 + 1013904223;
- int i1 = this.randValue >> 2;
+ int i1 = this.random.nextInt() >> 2; // SparklyPaper - parallel world ticking
out.set(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); // Paper - change to setValues call
return out; // Paper
diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java
index 0003fb51ae3a6575575e10b4c86719f3061e2577..0c5e0634852fd929f7f1c4373b0c7baebb07bc96 100644
--- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java
@@ -115,9 +115,9 @@ public class CactusBlock extends Block {
@Override
public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper
- CraftEventFactory.blockDamage = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); // CraftBukkit
+ CraftEventFactory.blockDamageRT.set(world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ())); // CraftBukkit // SparklyPaper - parallel world ticking
entity.hurt(world.damageSources().cactus(), 1.0F);
- CraftEventFactory.blockDamage = null; // CraftBukkit
+ CraftEventFactory.blockDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking
}
@Override
diff --git a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java
index 7700461b8cd0bde1bf6c0d5e4b73184bed1adc4e..e55dd626d3ead2655894ddb9a25a31b661810533 100644
--- a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java
@@ -95,9 +95,9 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB
public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper
if ((Boolean) state.getValue(CampfireBlock.LIT) && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity) entity)) {
- org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = CraftBlock.at(world, pos); // CraftBukkit
+ org.bukkit.craftbukkit.event.CraftEventFactory.blockDamageRT.set(CraftBlock.at(world, pos)); // CraftBukkit // SparklyPaper - parallel world ticking
entity.hurt(world.damageSources().inFire(), (float) this.fireDamage);
- org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = null; // CraftBukkit
+ org.bukkit.craftbukkit.event.CraftEventFactory.blockDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking
}
super.entityInside(state, world, pos, entity);
diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java
index 9b1e51c1d95da885c80c6d05000d83436b7bcfb4..720b86021fd94cb68440eafebab4ea5d6e380c5f 100644
--- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java
@@ -48,7 +48,8 @@ public class DispenserBlock extends BaseEntityBlock {
object2objectopenhashmap.defaultReturnValue(new DefaultDispenseItemBehavior());
});
private static final int TRIGGER_DURATION = 4;
- public static boolean eventFired = false; // CraftBukkit
+ // public static boolean eventFired = false; // CraftBukkit // SparklyPaper - parallel world ticking
+ public static ThreadLocal<Boolean> eventFired = ThreadLocal.withInitial(() -> Boolean.FALSE); // SparklyPaper - parallel world ticking
public static void registerBehavior(ItemLike provider, DispenseItemBehavior behavior) {
DispenserBlock.DISPENSER_REGISTRY.put(provider.asItem(), behavior);
@@ -99,7 +100,7 @@ public class DispenserBlock extends BaseEntityBlock {
if (idispensebehavior != DispenseItemBehavior.NOOP) {
if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - BlockPreDispenseEvent is called here
- DispenserBlock.eventFired = false; // CraftBukkit - reset event status
+ DispenserBlock.eventFired.set(Boolean.FALSE); // CraftBukkit - reset event status // SparklyPaper - parallel world ticking
tileentitydispenser.setItem(i, idispensebehavior.dispense(sourceblock, itemstack));
}
diff --git a/src/main/java/net/minecraft/world/level/block/FungusBlock.java b/src/main/java/net/minecraft/world/level/block/FungusBlock.java
index 17e9e2efc78cfe8577dbf4e1d6096543ad8b8ac4..0d56dff0ccb86bc62b7752489f4b5d3fa4f1b913 100644
--- a/src/main/java/net/minecraft/world/level/block/FungusBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/FungusBlock.java
@@ -61,9 +61,9 @@ public class FungusBlock extends BushBlock implements BonemealableBlock {
this.getFeature(world).ifPresent((holder) -> {
// CraftBukkit start
if (this == Blocks.WARPED_FUNGUS) {
- SaplingBlock.treeType = org.bukkit.TreeType.WARPED_FUNGUS;
+ SaplingBlock.treeTypeRT.set(org.bukkit.TreeType.WARPED_FUNGUS); // SparklyPaper - parallel world ticking
} else if (this == Blocks.CRIMSON_FUNGUS) {
- SaplingBlock.treeType = org.bukkit.TreeType.CRIMSON_FUNGUS;
+ SaplingBlock.treeTypeRT.set(org.bukkit.TreeType.CRIMSON_FUNGUS); // SparklyPaper - parallel world ticking
}
// CraftBukkit end
((ConfiguredFeature) holder.value()).place(world, world.getChunkSource().getGenerator(), random, pos);
diff --git a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java
index 1b766045687e4dcded5cbcc50b746c55b9a34e22..50e3ee93ed989a9a16f6d652a825abad488bb0b0 100644
--- a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java
@@ -23,9 +23,9 @@ public class MagmaBlock extends Block {
@Override
public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) {
if (!entity.isSteppingCarefully() && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity) entity)) {
- org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); // CraftBukkit
+ org.bukkit.craftbukkit.event.CraftEventFactory.blockDamageRT.set(world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ())); // CraftBukkit // SparklyPaper - parallel world ticking
entity.hurt(world.damageSources().hotFloor(), 1.0F);
- org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = null; // CraftBukkit
+ org.bukkit.craftbukkit.event.CraftEventFactory.blockDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking
}
super.stepOn(world, pos, state, entity);
diff --git a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java
index 302c5a6401facf192677b89cc0e9190bb35b1229..6d4f5383d30893803945803384d4fdd7e1d17a51 100644
--- a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java
@@ -93,7 +93,7 @@ public class MushroomBlock extends BushBlock implements BonemealableBlock {
return false;
} else {
world.removeBlock(pos, false);
- SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? TreeType.BROWN_MUSHROOM : TreeType.RED_MUSHROOM; // CraftBukkit // Paper
+ SaplingBlock.treeTypeRT.set((this == Blocks.BROWN_MUSHROOM) ? TreeType.BROWN_MUSHROOM : TreeType.RED_MUSHROOM); // CraftBukkit // Paper // SparklyPaper - parallel world ticking
if (((ConfiguredFeature) ((Holder) optional.get()).value()).place(world, world.getChunkSource().getGenerator(), random, pos)) {
return true;
} else {
diff --git a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java
index cd943997f11f5ea5c600fdc6db96043fb0fa713c..fdfdac600fab86822cb9d1196b43424ad3fcf92a 100644
--- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java
@@ -141,9 +141,9 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate
@Override
public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
if (state.getValue(PointedDripstoneBlock.TIP_DIRECTION) == Direction.UP && state.getValue(PointedDripstoneBlock.THICKNESS) == DripstoneThickness.TIP) {
- CraftEventFactory.blockDamage = CraftBlock.at(world, pos); // CraftBukkit
+ CraftEventFactory.blockDamageRT.set(CraftBlock.at(world, pos)); // CraftBukkit // SparklyPaper - parallel world ticking
entity.causeFallDamage(fallDistance + 2.0F, 2.0F, world.damageSources().stalagmite());
- CraftEventFactory.blockDamage = null; // CraftBukkit
+ CraftEventFactory.blockDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking
} else {
super.fallOn(world, state, pos, entity, fallDistance);
}
diff --git a/src/main/java/net/minecraft/world/level/block/SaplingBlock.java b/src/main/java/net/minecraft/world/level/block/SaplingBlock.java
index 53ac4e618fec3fe384d8a106c521f3eace0b5b35..b29df252c1b039004f40a8cf9aefb8387f29d7e3 100644
--- a/src/main/java/net/minecraft/world/level/block/SaplingBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/SaplingBlock.java
@@ -27,7 +27,7 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock {
protected static final float AABB_OFFSET = 6.0F;
protected static final VoxelShape SHAPE = Block.box(2.0D, 0.0D, 2.0D, 14.0D, 12.0D, 14.0D);
private final AbstractTreeGrower treeGrower;
- public static TreeType treeType; // CraftBukkit
+ public static final ThreadLocal<TreeType> treeTypeRT = new ThreadLocal<>(); // CraftBukkit // SparklyPaper - parallel world ticking (from Folia)
protected SaplingBlock(AbstractTreeGrower generator, BlockBehaviour.Properties settings) {
super(settings);
@@ -60,8 +60,8 @@ public class SaplingBlock extends BushBlock implements BonemealableBlock {
this.treeGrower.growTree(world, world.getChunkSource().getGenerator(), pos, state, random);
world.captureTreeGeneration = false;
if (world.capturedBlockStates.size() > 0) {
- TreeType treeType = SaplingBlock.treeType;
- SaplingBlock.treeType = null;
+ TreeType treeType = SaplingBlock.treeTypeRT.get(); // SparklyPaper - parallel world ticking
+ SaplingBlock.treeTypeRT.set(null); // SparklyPaper - parallel world ticking
Location location = CraftLocation.toBukkit(pos, world.getWorld());
java.util.List<BlockState> blocks = new java.util.ArrayList<>(world.capturedBlockStates.values());
world.capturedBlockStates.clear();
diff --git a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java
index 34eb7ba1adb51e394bf46a6f643db3529626d9ec..d48945137f717062e5233c10e978e5134edebf19 100644
--- a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java
@@ -85,9 +85,9 @@ public class SweetBerryBushBlock extends BushBlock implements BonemealableBlock
double d1 = Math.abs(entity.getZ() - entity.zOld);
if (d0 >= 0.003000000026077032D || d1 >= 0.003000000026077032D) {
- CraftEventFactory.blockDamage = CraftBlock.at(world, pos); // CraftBukkit
+ CraftEventFactory.blockDamageRT.set(CraftBlock.at(world, pos)); // CraftBukkit // SparklyPaper - parallel world ticking
entity.hurt(world.damageSources().sweetBerryBush(), 1.0F);
- CraftEventFactory.blockDamage = null; // CraftBukkit
+ CraftEventFactory.blockDamageRT.set(null); // CraftBukkit // SparklyPaper - parallel world ticking
}
}
diff --git a/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
index c134d089e55ea2ffb180f92aea020bd7647259c9..a7e1e05fdcb380de71b1bac5f7b5daca3dee2ac9 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
@@ -78,6 +78,12 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co
return canUnlock(player, lock, containerName, null);
}
public static boolean canUnlock(Player player, LockCode lock, Component containerName, @Nullable BlockEntity blockEntity) {
+ // SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch)
+ if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != serverPlayer.serverLevel()) {
+ net.minecraft.server.MinecraftServer.LOGGER.warn("Player " + serverPlayer.getScoreboardName() + " (" + serverPlayer.getStringUUID() + ") attempted to open a BlockEntity @ " + blockEntity.getLevel().getWorld().getName() + " " + blockEntity.getBlockPos().getX() + ", " + blockEntity.getBlockPos().getY() + ", " + blockEntity.getBlockPos().getZ() + " while they were in a different world " + serverPlayer.level().getWorld().getName() + " than the block themselves!");
+ return false;
+ }
+ // SparklyPaper end
if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != null && blockEntity.getLevel().getBlockEntity(blockEntity.getBlockPos()) == blockEntity) {
final org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(blockEntity.getLevel(), blockEntity.getBlockPos());
net.kyori.adventure.text.Component lockedMessage = net.kyori.adventure.text.Component.translatable("container.isLocked", io.papermc.paper.adventure.PaperAdventure.asAdventure(containerName));
diff --git a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
index 963a596154091b79ca139af6274aa323518ad1ad..2817225e5efc97d24e54800689cdda0f53baa616 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
@@ -235,11 +235,11 @@ public class ConduitBlockEntity extends BlockEntity {
if (blockEntity.destroyTarget != null) {
// CraftBukkit start
- CraftEventFactory.blockDamage = CraftBlock.at(world, pos);
+ CraftEventFactory.blockDamageRT.set(CraftBlock.at(world, pos)); // SparklyPaper - parallel world ticking
if (blockEntity.destroyTarget.hurt(world.damageSources().magic(), 4.0F)) {
world.playSound((Player) null, blockEntity.destroyTarget.getX(), blockEntity.destroyTarget.getY(), blockEntity.destroyTarget.getZ(), SoundEvents.CONDUIT_ATTACK_TARGET, SoundSource.BLOCKS, 1.0F, 1.0F);
}
- CraftEventFactory.blockDamage = null;
+ CraftEventFactory.blockDamageRT.set(null); // SparklyPaper - parallel world ticking
// CraftBukkit end
}
diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
index 65112ec3a6ea1c27f032477720ae74395523012b..41ed21bbb7762e52cd800846f546277c19ecd67e 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
@@ -43,9 +43,9 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi
// Paper end
public static void serverTick(Level world, BlockPos pos, BlockState state, SculkCatalystBlockEntity blockEntity) {
- org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = blockEntity.getBlockPos(); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep.
+ org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverrideRT.set(blockEntity.getBlockPos()); // SparklyPaper - parallel world ticking // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep.
blockEntity.catalystListener.getSculkSpreader().updateCursors(world, pos, world.getRandom(), true);
- org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = null; // CraftBukkit
+ org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverrideRT.set(null); // SparklyPaper - parallel world ticking // CraftBukkit
}
@Override
diff --git a/src/main/java/net/minecraft/world/level/block/grower/AbstractTreeGrower.java b/src/main/java/net/minecraft/world/level/block/grower/AbstractTreeGrower.java
index a743f36f2682a6b72ffa6644782fc081d1479eb7..85e7da9884f48989a62a789306b701a3dc1a3b0d 100644
--- a/src/main/java/net/minecraft/world/level/block/grower/AbstractTreeGrower.java
+++ b/src/main/java/net/minecraft/world/level/block/grower/AbstractTreeGrower.java
@@ -75,51 +75,53 @@ public abstract class AbstractTreeGrower {
// CraftBukkit start
protected void setTreeType(Holder<ConfiguredFeature<?, ?>> holder) {
ResourceKey<ConfiguredFeature<?, ?>> worldgentreeabstract = holder.unwrapKey().get();
+ TreeType treeType; // SparklyPaper - parallel world ticking
if (worldgentreeabstract == TreeFeatures.OAK || worldgentreeabstract == TreeFeatures.OAK_BEES_005) {
- SaplingBlock.treeType = TreeType.TREE;
+ treeType = TreeType.TREE;
} else if (worldgentreeabstract == TreeFeatures.HUGE_RED_MUSHROOM) {
- SaplingBlock.treeType = TreeType.RED_MUSHROOM;
+ treeType = TreeType.RED_MUSHROOM;
} else if (worldgentreeabstract == TreeFeatures.HUGE_BROWN_MUSHROOM) {
- SaplingBlock.treeType = TreeType.BROWN_MUSHROOM;
+ treeType = TreeType.BROWN_MUSHROOM;
} else if (worldgentreeabstract == TreeFeatures.JUNGLE_TREE) {
- SaplingBlock.treeType = TreeType.COCOA_TREE;
+ treeType = TreeType.COCOA_TREE;
} else if (worldgentreeabstract == TreeFeatures.JUNGLE_TREE_NO_VINE) {
- SaplingBlock.treeType = TreeType.SMALL_JUNGLE;
+ treeType = TreeType.SMALL_JUNGLE;
} else if (worldgentreeabstract == TreeFeatures.PINE) {
- SaplingBlock.treeType = TreeType.TALL_REDWOOD;
+ treeType = TreeType.TALL_REDWOOD;
} else if (worldgentreeabstract == TreeFeatures.SPRUCE) {
- SaplingBlock.treeType = TreeType.REDWOOD;
+ treeType = TreeType.REDWOOD;
} else if (worldgentreeabstract == TreeFeatures.ACACIA) {
- SaplingBlock.treeType = TreeType.ACACIA;
+ treeType = TreeType.ACACIA;
} else if (worldgentreeabstract == TreeFeatures.BIRCH || worldgentreeabstract == TreeFeatures.BIRCH_BEES_005) {
- SaplingBlock.treeType = TreeType.BIRCH;
+ treeType = TreeType.BIRCH;
} else if (worldgentreeabstract == TreeFeatures.SUPER_BIRCH_BEES_0002) {
- SaplingBlock.treeType = TreeType.TALL_BIRCH;
+ treeType = TreeType.TALL_BIRCH;
} else if (worldgentreeabstract == TreeFeatures.SWAMP_OAK) {
- SaplingBlock.treeType = TreeType.SWAMP;
+ treeType = TreeType.SWAMP;
} else if (worldgentreeabstract == TreeFeatures.FANCY_OAK || worldgentreeabstract == TreeFeatures.FANCY_OAK_BEES_005) {
- SaplingBlock.treeType = TreeType.BIG_TREE;
+ treeType = TreeType.BIG_TREE;
} else if (worldgentreeabstract == TreeFeatures.JUNGLE_BUSH) {
- SaplingBlock.treeType = TreeType.JUNGLE_BUSH;
+ treeType = TreeType.JUNGLE_BUSH;
} else if (worldgentreeabstract == TreeFeatures.DARK_OAK) {
- SaplingBlock.treeType = TreeType.DARK_OAK;
+ treeType = TreeType.DARK_OAK;
} else if (worldgentreeabstract == TreeFeatures.MEGA_SPRUCE) {
- SaplingBlock.treeType = TreeType.MEGA_REDWOOD;
+ treeType = TreeType.MEGA_REDWOOD;
} else if (worldgentreeabstract == TreeFeatures.MEGA_PINE) {
- SaplingBlock.treeType = TreeType.MEGA_REDWOOD;
+ treeType = TreeType.MEGA_REDWOOD;
} else if (worldgentreeabstract == TreeFeatures.MEGA_JUNGLE_TREE) {
- SaplingBlock.treeType = TreeType.JUNGLE;
+ treeType = TreeType.JUNGLE;
} else if (worldgentreeabstract == TreeFeatures.AZALEA_TREE) {
- SaplingBlock.treeType = TreeType.AZALEA;
+ treeType = TreeType.AZALEA;
} else if (worldgentreeabstract == TreeFeatures.MANGROVE) {
- SaplingBlock.treeType = TreeType.MANGROVE;
+ treeType = TreeType.MANGROVE;
} else if (worldgentreeabstract == TreeFeatures.TALL_MANGROVE) {
- SaplingBlock.treeType = TreeType.TALL_MANGROVE;
+ treeType = TreeType.TALL_MANGROVE;
} else if (worldgentreeabstract == TreeFeatures.CHERRY || worldgentreeabstract == TreeFeatures.CHERRY_BEES_005) {
- SaplingBlock.treeType = TreeType.CHERRY;
+ treeType = TreeType.CHERRY;
} else {
throw new IllegalArgumentException("Unknown tree generator " + worldgentreeabstract);
}
+ SaplingBlock.treeTypeRT.set(treeType); // SparklyPaper - parallel world ticking
}
// CraftBukkit end
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
index fa170cc1ce7011d201295b89718292d696c7fc24..59045493537533f60bb5e3b80633c71bafb25dc0 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -412,6 +412,7 @@ public class LevelChunk extends ChunkAccess {
@Nullable
public BlockState setBlockState(BlockPos blockposition, BlockState iblockdata, boolean flag, boolean doPlace) {
+ io.papermc.paper.util.TickThread.ensureTickThread(this.level, blockposition, "Updating block asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
// CraftBukkit end
int i = blockposition.getY();
LevelChunkSection chunksection = this.getSection(this.getSectionIndex(i));
diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
index 4cdfc433df67afcd455422e9baf56f167dd712ae..f52b3740bd48f8527a36d48a0454e7d601763985 100644
--- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
@@ -9,6 +9,13 @@ import net.minecraft.world.entity.Entity;
public class EntityTickList {
private final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Entity> entities = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(true); // Paper - rewrite this, always keep this updated - why would we EVER tick an entity that's not ticking?
+ // SparklyPaper start - parallel world ticking
+ // Used to track async entity additions/removals/loops
+ private final net.minecraft.server.level.ServerLevel serverLevel;
+ public EntityTickList(net.minecraft.server.level.ServerLevel serverLevel) {
+ this.serverLevel = serverLevel;
+ }
+ // SparklyPaper end
private void ensureActiveIsNotIterated() {
// Paper - replace with better logic, do not delay removals
@@ -16,13 +23,13 @@ public class EntityTickList {
}
public void add(Entity entity) {
- io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist addition"); // Paper
+ io.papermc.paper.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist addition"); // Paper // SparklyPaper - parallel world ticking (additional concurrency issues logs)
this.ensureActiveIsNotIterated();
this.entities.add(entity); // Paper - replace with better logic, do not delay removals/additions
}
public void remove(Entity entity) {
- io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist removal"); // Paper
+ io.papermc.paper.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist removal"); // Paper // SparklyPaper - parallel world ticking (additional concurrency issues logs)
this.ensureActiveIsNotIterated();
this.entities.remove(entity); // Paper - replace with better logic, do not delay removals/additions
}
@@ -32,7 +39,7 @@ public class EntityTickList {
}
public void forEach(Consumer<Entity> action) {
- io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist iteration"); // Paper
+ io.papermc.paper.util.TickThread.ensureTickThread(serverLevel, "Asynchronous entity ticklist iteration"); // Paper // SparklyPaper - parallel world ticking (additional concurrency issues logs)
// Paper start - replace with better logic, do not delay removals/additions
// To ensure nothing weird happens with dimension travelling, do not iterate over new entries...
// (by dfl iterator() is configured to not iterate over new entries)
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index c3060d1d4d0caf369c6ab516cb424f45eb851019..80cc42ea129a796a3e1189d9f840ec8180b92229 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -426,7 +426,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
}
private boolean unloadChunk0(int x, int z, boolean save) {
- org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot
+ io.papermc.paper.util.TickThread.ensureTickThread(this.world, x, z, "Cannot unload chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
if (!this.isChunkLoaded(x, z)) {
return true;
}
@@ -441,7 +441,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public boolean regenerateChunk(int x, int z) {
- org.spigotmc.AsyncCatcher.catchOp("chunk regenerate"); // Spigot
+ io.papermc.paper.util.TickThread.ensureTickThread(this.world, x, z, "Cannot regenerate chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
warnUnsafeChunk("regenerating a faraway chunk", x, z); // Paper
// Paper start - implement regenerateChunk method
final ServerLevel serverLevel = this.world;
@@ -502,6 +502,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public boolean refreshChunk(int x, int z) {
+ io.papermc.paper.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;
@@ -537,7 +538,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public boolean loadChunk(int x, int z, boolean generate) {
- org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot
+ io.papermc.paper.util.TickThread.ensureTickThread(this.getHandle(), x, z, "May not sync load chunks asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
warnUnsafeChunk("loading a faraway chunk", x, z); // Paper
// Paper start - Optimize this method
ChunkPos chunkPos = new ChunkPos(x, z);
@@ -800,6 +801,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) {
+ io.papermc.paper.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);
@@ -910,11 +912,13 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source) {
+ io.papermc.paper.util.TickThread.ensureTickThread(this.world, x, z, "Cannot create explosion asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
return !this.world.explode(source == null ? null : ((CraftEntity) source).getHandle(), x, y, z, power, setFire, breakBlocks ? net.minecraft.world.level.Level.ExplosionInteraction.MOB : net.minecraft.world.level.Level.ExplosionInteraction.NONE).wasCanceled;
}
// Paper start
@Override
public boolean createExplosion(Entity source, Location loc, float power, boolean setFire, boolean breakBlocks) {
+ io.papermc.paper.util.TickThread.ensureTickThread(world, loc.getX(), loc.getZ(), "Cannot create explosion asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
return !world.explode(source != null ? ((org.bukkit.craftbukkit.entity.CraftEntity) source).getHandle() : null, loc.getX(), loc.getY(), loc.getZ(), power, setFire, breakBlocks ? net.minecraft.world.level.Level.ExplosionInteraction.MOB : net.minecraft.world.level.Level.ExplosionInteraction.NONE).wasCanceled;
}
// Paper end
@@ -984,6 +988,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) {
+ io.papermc.paper.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);
@@ -1014,6 +1019,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public void setBiome(int x, int y, int z, Holder<net.minecraft.world.level.biome.Biome> bb) {
BlockPos pos = new BlockPos(x, 0, z);
+ io.papermc.paper.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);
@@ -2272,6 +2278,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) {
+ io.papermc.paper.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())), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position));
}
// Paper end
@@ -2426,7 +2433,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
// Paper start
public java.util.concurrent.CompletableFuture<Chunk> getChunkAtAsync(int x, int z, boolean gen, boolean urgent) {
warnUnsafeChunk("getting a faraway chunk async", x, z); // Paper
- if (Bukkit.isPrimaryThread()) {
+ if (io.papermc.paper.util.TickThread.isTickThreadFor(this.getHandle(), x, z)) { // SparklyPaper - parallel world ticking
net.minecraft.world.level.chunk.LevelChunk immediate = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z);
if (immediate != null) {
return java.util.concurrent.CompletableFuture.completedFuture(new CraftChunk(immediate));
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
index bec8e6b62dba2bd0e4e85a7d1fb51287384f1290..f650163cab8c54b97a7dac7c79320dae733e3411 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
@@ -74,6 +74,11 @@ public class CraftBlock implements Block {
}
public net.minecraft.world.level.block.state.BlockState getNMS() {
+ // Folia start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously");
+ }
+ // Folia end - parallel world ticking
return this.world.getBlockState(this.position);
}
@@ -150,6 +155,11 @@ public class CraftBlock implements Block {
}
private void setData(final byte data, int flag) {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flag);
}
@@ -191,6 +201,12 @@ public class CraftBlock implements Block {
}
public static boolean setTypeAndData(LevelAccessor world, BlockPos position, net.minecraft.world.level.block.state.BlockState old, net.minecraft.world.level.block.state.BlockState blockData, boolean applyPhysics) {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "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 tile entity cleanup
if (old.hasBlockEntity() && blockData.getBlock() != old.getBlock()) { // SPIGOT-3725 remove old tile entity if block changes
// SPIGOT-4612: faster - just clear tile
@@ -336,18 +352,33 @@ public class CraftBlock implements Block {
@Override
public Biome getBiome() {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ());
}
// Paper start
@Override
public Biome getComputedBiome() {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ());
}
// Paper end
@Override
public void setBiome(Biome bio) {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio);
}
@@ -395,6 +426,11 @@ public class CraftBlock implements Block {
@Override
public boolean isBlockFaceIndirectlyPowered(BlockFace face) {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
int power = this.world.getMinecraftWorld().getSignal(this.position, CraftBlock.blockFaceToNotch(face));
Block relative = this.getRelative(face);
@@ -407,6 +443,11 @@ public class CraftBlock implements Block {
@Override
public int getBlockPower(BlockFace face) {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
int power = 0;
net.minecraft.world.level.Level world = this.world.getMinecraftWorld();
int x = this.getX();
@@ -477,6 +518,11 @@ public class CraftBlock implements Block {
@Override
public boolean breakNaturally() {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
return this.breakNaturally(null);
}
@@ -536,6 +582,11 @@ public class CraftBlock implements Block {
@Override
public boolean applyBoneMeal(BlockFace face) {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
Direction direction = CraftBlock.blockFaceToNotch(face);
BlockFertilizeEvent event = null;
ServerLevel world = this.getCraftWorld().getHandle();
@@ -547,8 +598,8 @@ public class CraftBlock implements Block {
world.captureTreeGeneration = false;
if (world.capturedBlockStates.size() > 0) {
- TreeType treeType = SaplingBlock.treeType;
- SaplingBlock.treeType = null;
+ TreeType treeType = SaplingBlock.treeTypeRT.get(); // SparklyPaper - parallel world ticking
+ SaplingBlock.treeTypeRT.set(null); // SparklyPaper - parallel world ticking
List<BlockState> blocks = new ArrayList<>(world.capturedBlockStates.values());
world.capturedBlockStates.clear();
StructureGrowEvent structureEvent = null;
@@ -637,6 +688,11 @@ public class CraftBlock implements Block {
@Override
public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode) {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
Preconditions.checkArgument(start != null, "Location start cannot be null");
Preconditions.checkArgument(this.getWorld().equals(start.getWorld()), "Location start cannot be a different world");
start.checkFinite();
@@ -678,6 +734,11 @@ public class CraftBlock implements Block {
@Override
public boolean canPlace(BlockData data) {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
Preconditions.checkArgument(data != null, "BlockData cannot be null");
net.minecraft.world.level.block.state.BlockState iblockdata = ((CraftBlockData) data).getState();
net.minecraft.world.level.Level world = this.world.getMinecraftWorld();
@@ -712,6 +773,11 @@ public class CraftBlock implements Block {
@Override
public void tick() {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
net.minecraft.world.level.block.state.BlockState blockData = this.getNMS();
net.minecraft.server.level.ServerLevel level = this.world.getMinecraftWorld();
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
index 390e1b7fd2721b99cb3ce268c6bc1bf0a38e08a3..9255e51954bd9a43afc366d8c414dd8af7571525 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
@@ -210,6 +210,12 @@ public class CraftBlockState implements BlockState {
LevelAccessor access = this.getWorldHandle();
CraftBlock block = this.getBlock();
+ // SparklyPaper start - parallel world ticking
+ if (access instanceof net.minecraft.server.level.ServerLevel serverWorld) {
+ io.papermc.paper.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
+
if (block.getType() != this.getType()) {
if (!force) {
return false;
@@ -350,6 +356,7 @@ public class CraftBlockState implements BlockState {
@Override
public java.util.Collection<org.bukkit.inventory.ItemStack> getDrops(org.bukkit.inventory.ItemStack item, org.bukkit.entity.Entity entity) {
+ io.papermc.paper.util.TickThread.ensureTickThread(world.getHandle(), position, "Cannot modify world asynchronously"); // SparklyPaper - parallel world ticking
net.minecraft.world.item.ItemStack nms = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item);
// Modelled off EntityHuman#hasBlock
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
index 5dc160b743534665c6b3efb10b10f7c36e2da5ab..8942bb585e1f4a0b747194ef2ad91acc5de82d8b 100644
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
@@ -246,8 +246,8 @@ import org.bukkit.potion.PotionEffect;
import org.bukkit.util.Vector;
public class CraftEventFactory {
- public static org.bukkit.block.Block blockDamage; // For use in EntityDamageByBlockEvent
- public static Entity entityDamage; // For use in EntityDamageByEntityEvent
+ public static final ThreadLocal<org.bukkit.block.Block> blockDamageRT = new ThreadLocal<>(); // For use in EntityDamageByBlockEvent // SparklyPaper - parallel world ticking (this is from Folia, fixes concurrency bugs / For use in EntityDamageByEntityEvent)
+ public static final ThreadLocal<Entity> entityDamageRT = new ThreadLocal<>(); // For use in EntityDamageByEntityEvent // SparklyPaper - parallel world ticking (this is from Folia, fixes concurrency bugs / For use in EntityDamageByEntityEvent)
// helper methods
private static boolean canBuild(ServerLevel world, Player player, int x, int z) {
@@ -920,7 +920,7 @@ public class CraftEventFactory {
return CraftEventFactory.handleBlockSpreadEvent(world, source, target, block, 2);
}
- public static BlockPos sourceBlockOverride = null; // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep.
+ public static final ThreadLocal<BlockPos> sourceBlockOverrideRT = new ThreadLocal<>(); // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. // SparklyPaper - parallel world ticking (this is from Folia, fixes concurrency bugs with sculk catalysts)
public static boolean handleBlockSpreadEvent(LevelAccessor world, BlockPos source, BlockPos target, net.minecraft.world.level.block.state.BlockState block, int flag) {
// Suppress during worldgen
@@ -932,7 +932,7 @@ public class CraftEventFactory {
CraftBlockState state = CraftBlockStates.getBlockState(world, target, flag);
state.setData(block);
- BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverride != null ? CraftEventFactory.sourceBlockOverride : source), state);
+ BlockSpreadEvent event = new BlockSpreadEvent(state.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverrideRT.get() != null ? CraftEventFactory.sourceBlockOverrideRT.get() : source), state); // SparklyPaper - parallel world ticking
Bukkit.getPluginManager().callEvent(event);
if (!event.isCancelled()) {
@@ -1047,8 +1047,8 @@ public class CraftEventFactory {
private static EntityDamageEvent handleEntityDamageEvent(Entity entity, DamageSource source, Map<DamageModifier, Double> modifiers, Map<DamageModifier, Function<? super Double, Double>> modifierFunctions, boolean cancelled) {
if (source.is(DamageTypeTags.IS_EXPLOSION)) {
DamageCause damageCause;
- Entity damager = CraftEventFactory.entityDamage;
- CraftEventFactory.entityDamage = null;
+ Entity damager = CraftEventFactory.entityDamageRT.get(); // SparklyPaper - parallel world ticking
+ CraftEventFactory.entityDamageRT.set(null); // SparklyPaper - parallel world ticking
EntityDamageEvent event;
if (damager == null) {
event = new EntityDamageByBlockEvent(null, entity.getBukkitEntity(), DamageCause.BLOCK_EXPLOSION, modifiers, modifierFunctions, source.explodedBlockState); // Paper - handle block state in damage
@@ -1109,13 +1109,13 @@ public class CraftEventFactory {
}
return event;
} else if (source.is(DamageTypes.LAVA)) {
- EntityDamageEvent event = (new EntityDamageByBlockEvent(CraftEventFactory.blockDamage, entity.getBukkitEntity(), DamageCause.LAVA, modifiers, modifierFunctions));
+ EntityDamageEvent event = (new EntityDamageByBlockEvent(CraftEventFactory.blockDamageRT.get(), entity.getBukkitEntity(), DamageCause.LAVA, modifiers, modifierFunctions)); // SparklyPaper - parallel world ticking
event.setCancelled(cancelled);
- Block damager = CraftEventFactory.blockDamage;
- CraftEventFactory.blockDamage = null; // SPIGOT-6639: Clear blockDamage to allow other entity damage during event call
+ Block damager = CraftEventFactory.blockDamageRT.get(); // SparklyPaper - parallel world ticking
+ CraftEventFactory.blockDamageRT.set(null); // SPIGOT-6639: Clear blockDamage to allow other entity damage during event call // SparklyPaper - parallel world ticking
CraftEventFactory.callEvent(event);
- CraftEventFactory.blockDamage = damager; // SPIGOT-6639: Re-set blockDamage so that other entities which are also getting damaged have the right cause
+ CraftEventFactory.blockDamageRT.set(damager); // SPIGOT-6639: Re-set blockDamage so that other entities which are also getting damaged have the right cause // SparklyPaper - parallel world ticking
if (!event.isCancelled()) {
event.getEntity().setLastDamageCause(event);
@@ -1123,9 +1123,9 @@ public class CraftEventFactory {
entity.lastDamageCancelled = true; // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Keep track if the event was canceled
}
return event;
- } else if (CraftEventFactory.blockDamage != null) {
+ } else if (CraftEventFactory.blockDamageRT.get() != null) { // SparklyPaper - parallel world ticking
DamageCause cause = null;
- Block damager = CraftEventFactory.blockDamage;
+ Block damager = CraftEventFactory.blockDamageRT.get(); // SparklyPaper - parallel world ticking
if (source.is(DamageTypes.CACTUS) || source.is(DamageTypes.SWEET_BERRY_BUSH) || source.is(DamageTypes.STALAGMITE) || source.is(DamageTypes.FALLING_STALACTITE) || source.is(DamageTypes.FALLING_ANVIL)) {
cause = DamageCause.CONTACT;
} else if (source.is(DamageTypes.HOT_FLOOR)) {
@@ -1140,9 +1140,9 @@ public class CraftEventFactory {
EntityDamageEvent event = new EntityDamageByBlockEvent(damager, entity.getBukkitEntity(), cause, modifiers, modifierFunctions);
event.setCancelled(cancelled);
- CraftEventFactory.blockDamage = null; // SPIGOT-6639: Clear blockDamage to allow other entity damage during event call
+ CraftEventFactory.blockDamageRT.set(null); // SPIGOT-6639: Clear blockDamage to allow other entity damage during event call // SparklyPaper - parallel world ticking
CraftEventFactory.callEvent(event);
- CraftEventFactory.blockDamage = damager; // SPIGOT-6639: Re-set blockDamage so that other entities which are also getting damaged have the right cause
+ CraftEventFactory.blockDamageRT.set(damager); // SPIGOT-6639: Re-set blockDamage so that other entities which are also getting damaged have the right cause // SparklyPaper - parallel world ticking
if (!event.isCancelled()) {
event.getEntity().setLastDamageCause(event);
@@ -1150,10 +1150,10 @@ public class CraftEventFactory {
entity.lastDamageCancelled = true; // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Keep track if the event was canceled
}
return event;
- } else if (CraftEventFactory.entityDamage != null) {
+ } else if (CraftEventFactory.entityDamageRT.get() != null) { // SparklyPaper - parallel world ticking
DamageCause cause = null;
- CraftEntity damager = CraftEventFactory.entityDamage.getBukkitEntity();
- CraftEventFactory.entityDamage = null;
+ CraftEntity damager = CraftEventFactory.entityDamageRT.get().getBukkitEntity(); // SparklyPaper - parallel world ticking
+ CraftEventFactory.entityDamageRT.set(null); // SparklyPaper - parallel world ticking
if (source.is(DamageTypes.FALLING_STALACTITE) || source.is(DamageTypes.FALLING_BLOCK) || source.is(DamageTypes.FALLING_ANVIL)) {
cause = DamageCause.FALLING_BLOCK;
} else if (damager instanceof LightningStrike) {
@@ -2123,7 +2123,7 @@ public class CraftEventFactory {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemStack.copyWithCount(1));
org.bukkit.event.block.BlockDispenseEvent event = new org.bukkit.event.block.BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(to.getX(), to.getY(), to.getZ()));
- if (!net.minecraft.world.level.block.DispenserBlock.eventFired) {
+ if (!net.minecraft.world.level.block.DispenserBlock.eventFired.get()) { // SparklyPaper - parallel world ticking
if (!event.callEvent()) {
return itemStack;
}
diff --git a/src/main/kotlin/net/sparklypower/sparklypaper/ServerLevelTickExecutorThreadFactory.kt b/src/main/kotlin/net/sparklypower/sparklypaper/ServerLevelTickExecutorThreadFactory.kt
new file mode 100644
index 0000000000000000000000000000000000000000..3d536f724ffdae462e3af39e85e4e39190696c37
--- /dev/null
+++ b/src/main/kotlin/net/sparklypower/sparklypaper/ServerLevelTickExecutorThreadFactory.kt
@@ -0,0 +1,21 @@
+package net.sparklypower.sparklypaper
+
+import io.papermc.paper.util.TickThread
+import java.util.concurrent.ThreadFactory
+import java.util.concurrent.atomic.AtomicInteger
+
+class ServerLevelTickExecutorThreadFactory(private val worldName: String) : ThreadFactory {
+ override fun newThread(p0: Runnable): Thread {
+ val tickThread = TickThread.ServerLevelTickThread(p0, "serverlevel-tick-worker [$worldName]")
+
+ if (tickThread.isDaemon) {
+ tickThread.isDaemon = false
+ }
+
+ if (tickThread.priority != 5) {
+ tickThread.priority = 5
+ }
+
+ return tickThread
+ }
+}
\ No newline at end of file