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

1898 lines
118 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/ca/spottedleaf/moonrise/common/util/TickThread.java b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java
index 217d1f908a36a5177ba3cbb80a33f73d4dab0fa0..aec59213525039d6cf38ab8c3acfd45116c75eed 100644
--- a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java
@@ -14,6 +14,7 @@ import java.util.concurrent.atomic.AtomicInteger;
public class TickThread extends Thread {
private static final Logger LOGGER = LoggerFactory.getLogger(TickThread.class);
+ public static final boolean HARD_THROW = !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
/**
* @deprecated
@@ -22,52 +23,121 @@ public class TickThread extends Thread {
public static void ensureTickThread(final String reason) {
if (!isTickThread()) {
LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
- throw new IllegalStateException(reason);
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
}
}
public static void ensureTickThread(final Level world, final BlockPos pos, final String reason) {
if (!isTickThreadFor(world, pos)) {
- LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
- throw new IllegalStateException(reason);
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " blockPos: " + pos + " - " + getTickThreadInformation(world.getServer()), new Throwable());
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
}
}
public static void ensureTickThread(final Level world, final ChunkPos pos, final String reason) {
if (!isTickThreadFor(world, pos)) {
- LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
- throw new IllegalStateException(reason);
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " chunkPos: " + pos + " - " + getTickThreadInformation(world.getServer()), new Throwable());
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
}
}
public static void ensureTickThread(final Level world, final int chunkX, final int chunkZ, final String reason) {
if (!isTickThreadFor(world, chunkX, chunkZ)) {
- LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
- throw new IllegalStateException(reason);
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " chunkX: " + chunkX + " chunkZ: " + chunkZ + " - " + getTickThreadInformation(world.getServer()), new Throwable());
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
}
}
public static void ensureTickThread(final Entity entity, final String reason) {
if (!isTickThreadFor(entity)) {
- LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
- throw new IllegalStateException(reason);
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ entity " + entity.getStringUUID() + " - " + getTickThreadInformation(entity.getServer()), new Throwable());
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
}
}
public static void ensureTickThread(final Level world, final AABB aabb, final String reason) {
if (!isTickThreadFor(world, aabb)) {
- LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
- throw new IllegalStateException(reason);
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " aabb: " + aabb + " - " + getTickThreadInformation(world.getServer()), new Throwable());
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
}
}
public static void ensureTickThread(final Level world, final double blockX, final double blockZ, final String reason) {
if (!isTickThreadFor(world, blockX, blockZ)) {
- LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
- throw new IllegalStateException(reason);
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " blockX: " + blockX + " blockZ: " + blockZ + " - " + getTickThreadInformation(world.getServer()), 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 net.minecraft.server.level.ServerLevel world, final String reason) {
+ if (!isTickThreadFor(world)) {
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason + " @ world " + world.getWorld().getName() + " - " + getTickThreadInformation(world.getServer()), new Throwable());
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
+ }
+ }
+
+ // 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) {
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread ONLY tick thread check: " + reason, 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 net.minecraft.server.level.ServerLevel world, final String reason) {
+ boolean isValidTickThread = isTickThreadFor(world);
+ boolean isAsyncThread = !isTickThread();
+ boolean isValid = isAsyncThread || isValidTickThread;
+ if (!isValid) {
+ LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread or async thread check: " + reason + " @ world " + world.getWorld().getName() + " - " + getTickThreadInformation(world.getServer()), new Throwable());
+ if (HARD_THROW)
+ throw new IllegalStateException(reason);
+ }
+ }
+
+ public static String getTickThreadInformation(net.minecraft.server.MinecraftServer minecraftServer) {
+ StringBuilder sb = new StringBuilder();
+ Thread currentThread = Thread.currentThread();
+ sb.append("Is tick thread? ");
+ sb.append(currentThread instanceof TickThread);
+ sb.append("; Is server level tick thread? ");
+ sb.append(currentThread instanceof ServerLevelTickThread);
+ if (currentThread instanceof ServerLevelTickThread serverLevelTickThread) {
+ sb.append("; Currently ticking level: ");
+ if (serverLevelTickThread.currentlyTickingServerLevel != null) {
+ sb.append(serverLevelTickThread.currentlyTickingServerLevel.getWorld().getName());
+ } else {
+ sb.append("null");
+ }
+ }
+ sb.append("; Is iterating over levels? ");
+ sb.append(minecraftServer.isIteratingOverLevels);
+ sb.append("; Are we going to hard throw? ");
+ sb.append(HARD_THROW);
+ return sb.toString();
+ }
+
+ public static boolean isServerLevelTickThread() {
+ return Thread.currentThread() instanceof ServerLevelTickThread;
+ }
+
public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */
private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
@@ -102,42 +172,111 @@ public class TickThread extends Thread {
}
public static boolean isTickThreadFor(final Level 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 Level 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 Level 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 Level 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 Level 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 Level 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 Level 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 Level 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 Level 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 Level 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 net.minecraft.server.level.ServerLevel currentlyTickingServerLevel;
}
+ // SparklyPaper end
}
diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
index 91a6f57f35fc1553159cca138a0619e703b2b014..dd49dd779f4aa45b27614b581b347b608b1ac64f 100644
--- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
@@ -1030,7 +1030,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) {
@@ -1056,7 +1056,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");
@@ -1338,7 +1338,7 @@ public final class ChunkHolderManager {
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/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java
index ddb264443f2e38b6348226016f9139727c588898..5a5451dda717656e02f11c71646f7afeeb0c2232 100644
--- a/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java
+++ b/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java
@@ -54,7 +54,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 8aae1d113e84dfad9f2b6f0bcd203ca6c68bc5ce..7fca48f233e98b63a5d8c9094561380866f3d0a2 100644
--- a/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java
+++ b/src/main/java/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java
@@ -87,7 +87,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 0d12605dc84dad49faa18bf1fd058c3c168623ee..1a240c6ac7980df94fc40ce8fc6c3c6096db8eef 100644
--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
@@ -111,7 +111,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);
}
@@ -170,7 +170,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);
}
@@ -225,7 +225,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);
}
@@ -280,7 +280,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);
}
@@ -354,7 +354,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);
}
@@ -419,7 +419,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);
}
@@ -457,7 +457,7 @@ public interface DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); // Paper - ignore stack size on damageable items
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);
}
@@ -519,7 +519,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);
}
@@ -548,8 +548,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();
@@ -588,7 +588,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);
}
@@ -631,7 +631,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);
}
@@ -680,7 +680,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);
}
@@ -742,7 +742,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);
}
@@ -824,7 +824,7 @@ public interface DispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); // Paper - ignore stack size on damageable items
BlockDispenseEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) list.get(0).getBukkitEntity());
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) {
world.getCraftServer().getPluginManager().callEvent(event);
}
diff --git a/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java
index bf8c511739265c6a9cd277752e844481598f8966..d087b35b212e8bda1f6d8be35ed73fc759718c5e 100644
--- a/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java
+++ b/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java
@@ -50,7 +50,7 @@ public class EquipmentDispenseItemBehavior extends DefaultDispenseItemBehavior {
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()) {
world.getCraftServer().getPluginManager().callEvent(event);
}
diff --git a/src/main/java/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java
index 3588896b7413be73ade6b3f8fd111d02c48ec550..913ef6c348af35053c93a37bc0ec719563740d56 100644
--- a/src/main/java/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java
+++ b/src/main/java/net/minecraft/core/dispenser/MinecartDispenseItemBehavior.java
@@ -74,7 +74,7 @@ public class MinecartDispenseItemBehavior extends DefaultDispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
BlockDispenseEvent event = new BlockDispenseEvent(block2, craftItem.clone(), new org.bukkit.util.Vector(vec3d1.x, vec3d1.y, vec3d1.z));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) {
worldserver.getCraftServer().getPluginManager().callEvent(event);
}
diff --git a/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java b/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java
index 54c72cf472e06e214eb61bd8615a0bb27690c807..e4bce6bd878be3405691062db2f05492f67cfafb 100644
--- a/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java
+++ b/src/main/java/net/minecraft/core/dispenser/ProjectileDispenseBehavior.java
@@ -43,7 +43,7 @@ public class ProjectileDispenseBehavior extends DefaultDispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) enumdirection.getStepX(), (double) enumdirection.getStepY(), (double) enumdirection.getStepZ()));
- if (!DispenserBlock.eventFired) {
+ if (!DispenserBlock.eventFired.get()) {
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 65ed3d77a51b8299517e0c165403b0c5ac413475..f999c33552c611df733da93dcb9699b15fa54a92 100644
--- a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
+++ b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
@@ -41,7 +41,7 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior {
CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); // Paper - ignore stack size on damageable items
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 8f9fde5489c0e1d0a91203536caddec5a9c96f6c..84617a7d6fae4a67f07d50a8e9410e359725b675 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 e168e5f433da40f3bdd5821451c3f3572b69a48c..2a9f9e37fcaf70bff89f5adfbf54a0d95a14bb3a 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -3,9 +3,6 @@ package net.minecraft.server;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
-import co.aikar.timings.Timings;
-import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
-import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
@@ -16,66 +13,22 @@ import com.mojang.datafixers.DataFixer;
import com.mojang.jtracy.DiscontinuousFrame;
import com.mojang.jtracy.TracyClient;
import com.mojang.logging.LogUtils;
+import com.mojang.serialization.Dynamic;
+import com.mojang.serialization.Lifecycle;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectArraySet;
-import java.awt.image.BufferedImage;
-import java.io.BufferedWriter;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.lang.management.ManagementFactory;
-import java.lang.management.ThreadInfo;
-import java.lang.management.ThreadMXBean;
-import java.net.Proxy;
-import java.nio.file.FileStore;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.security.KeyPair;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.locks.LockSupport;
-import java.util.function.BooleanSupplier;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import javax.annotation.Nullable;
-import javax.imageio.ImageIO;
-import net.minecraft.CrashReport;
-import net.minecraft.CrashReportCategory;
-import net.minecraft.FileUtil;
-import net.minecraft.ReportType;
-import net.minecraft.ReportedException;
-import net.minecraft.SharedConstants;
-import net.minecraft.SystemReport;
-import net.minecraft.Util;
+import joptsimple.OptionSet;
+import net.minecraft.*;
import net.minecraft.commands.CommandSource;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
-import net.minecraft.core.BlockPos;
-import net.minecraft.core.HolderGetter;
-import net.minecraft.core.LayeredRegistryAccess;
-import net.minecraft.core.Registry;
-import net.minecraft.core.RegistryAccess;
+import net.minecraft.core.*;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.worldgen.features.MiscOverworldFeatures;
import net.minecraft.gametest.framework.GameTestTicker;
+import net.minecraft.nbt.NbtException;
+import net.minecraft.nbt.ReportedNbtException;
import net.minecraft.network.chat.ChatDecorator;
import net.minecraft.network.chat.ChatType;
import net.minecraft.network.chat.Component;
@@ -86,6 +39,14 @@ import net.minecraft.network.protocol.status.ServerStatus;
import net.minecraft.obfuscate.DontObfuscate;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.bossevents.CustomBossEvents;
+import net.minecraft.server.dedicated.DedicatedServer;
+import net.minecraft.server.dedicated.DedicatedServerProperties;
+import net.minecraft.server.level.*;
+import net.minecraft.server.level.progress.ChunkProgressListener;
+import net.minecraft.server.level.progress.ChunkProgressListenerFactory;
+import net.minecraft.server.network.ServerConnectionListener;
+import net.minecraft.server.network.TextFilter;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.repository.Pack;
import net.minecraft.server.packs.repository.PackRepository;
@@ -98,25 +59,12 @@ import net.minecraft.server.players.PlayerList;
import net.minecraft.server.players.ServerOpListEntry;
import net.minecraft.server.players.UserWhiteList;
import net.minecraft.tags.TagLoader;
-import net.minecraft.util.Crypt;
-import net.minecraft.util.CryptException;
-import net.minecraft.util.ModCheck;
-import net.minecraft.util.Mth;
-import net.minecraft.util.NativeModuleLister;
-import net.minecraft.util.ProgressListener;
-import net.minecraft.util.RandomSource;
-import net.minecraft.util.SignatureValidator;
-import net.minecraft.util.TimeUtil;
+import net.minecraft.util.*;
import net.minecraft.util.datafix.DataFixers;
import net.minecraft.util.debugchart.RemoteDebugSampleType;
import net.minecraft.util.debugchart.SampleLogger;
import net.minecraft.util.debugchart.TpsDebugDimensions;
-import net.minecraft.util.profiling.EmptyProfileResults;
-import net.minecraft.util.profiling.ProfileResults;
-import net.minecraft.util.profiling.Profiler;
-import net.minecraft.util.profiling.ProfilerFiller;
-import net.minecraft.util.profiling.ResultField;
-import net.minecraft.util.profiling.SingleTickProfiler;
+import net.minecraft.util.profiling.*;
import net.minecraft.util.profiling.jfr.JvmProfiler;
import net.minecraft.util.profiling.jfr.callback.ProfiledDuration;
import net.minecraft.util.profiling.metrics.profiling.ActiveMetricsRecorder;
@@ -136,15 +84,7 @@ import net.minecraft.world.flag.FeatureFlagSet;
import net.minecraft.world.flag.FeatureFlags;
import net.minecraft.world.item.alchemy.PotionBrewing;
import net.minecraft.world.item.crafting.RecipeManager;
-import net.minecraft.world.level.ChunkPos;
-import net.minecraft.world.level.CustomSpawner;
-import net.minecraft.world.level.DataPackConfig;
-import net.minecraft.world.level.ForcedChunksSavedData;
-import net.minecraft.world.level.GameRules;
-import net.minecraft.world.level.GameType;
-import net.minecraft.world.level.Level;
-import net.minecraft.world.level.LevelSettings;
-import net.minecraft.world.level.WorldDataConfiguration;
+import net.minecraft.world.level.*;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.FuelValues;
@@ -152,55 +92,46 @@ import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.world.level.chunk.storage.ChunkIOErrorReporter;
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
import net.minecraft.world.level.dimension.LevelStem;
-import net.minecraft.world.level.levelgen.WorldOptions;
+import net.minecraft.world.level.levelgen.*;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
-import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
-import net.minecraft.world.level.storage.WorldData;
-import org.slf4j.Logger;
-
-// CraftBukkit start
-import com.mojang.serialization.Dynamic;
-import com.mojang.serialization.Lifecycle;
-import java.io.File;
-import java.util.Random;
-// import jline.console.ConsoleReader; // Paper
-import joptsimple.OptionSet;
-import net.minecraft.nbt.NbtException;
-import net.minecraft.nbt.ReportedNbtException;
-import net.minecraft.server.bossevents.CustomBossEvents;
-import net.minecraft.server.dedicated.DedicatedServer;
-import net.minecraft.server.dedicated.DedicatedServerProperties;
-import net.minecraft.server.level.DemoMode;
-import net.minecraft.server.level.PlayerRespawnLogic;
-import net.minecraft.server.level.ServerChunkCache;
-import net.minecraft.server.level.ServerLevel;
-import net.minecraft.server.level.ServerPlayer;
-import net.minecraft.server.level.ServerPlayerGameMode;
-import net.minecraft.server.level.progress.ChunkProgressListener;
-import net.minecraft.server.level.progress.ChunkProgressListenerFactory;
-import net.minecraft.server.network.ServerConnectionListener;
-import net.minecraft.server.network.TextFilter;
-import net.minecraft.world.level.levelgen.Heightmap;
-import net.minecraft.world.level.levelgen.PatrolSpawner;
-import net.minecraft.world.level.levelgen.PhantomSpawner;
-import net.minecraft.world.level.levelgen.WorldDimensions;
import net.minecraft.world.level.levelgen.presets.WorldPresets;
-import net.minecraft.world.level.storage.CommandStorage;
-import net.minecraft.world.level.storage.DimensionDataStorage;
-import net.minecraft.world.level.storage.LevelData;
-import net.minecraft.world.level.storage.LevelDataAndDimensions;
-import net.minecraft.world.level.storage.LevelResource;
-import net.minecraft.world.level.storage.LevelStorageSource;
-import net.minecraft.world.level.storage.LevelSummary;
-import net.minecraft.world.level.storage.PlayerDataStorage;
-import net.minecraft.world.level.storage.PrimaryLevelData;
-import net.minecraft.world.level.storage.ServerLevelData;
+import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
+import net.minecraft.world.level.storage.*;
import net.minecraft.world.level.validation.ContentValidationException;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.event.server.ServerLoadEvent;
+import org.slf4j.Logger;
+
+import javax.annotation.Nullable;
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.net.Proxy;
+import java.nio.file.FileStore;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.LockSupport;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
// CraftBukkit end
@@ -335,6 +266,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
public final Set<Entity> entitiesWithScheduledTasks = java.util.concurrent.ConcurrentHashMap.newKeySet(); // SparklyPaper - skip EntityScheduler's executeTick checks if there isn't any tasks to be run (concurrent because plugins may schedule tasks async)
public net.sparklypower.sparklypaper.HalloweenManager halloweenManager = new net.sparklypower.sparklypaper.HalloweenManager(); // SparklyPaper - Spooky month optimizations
+ // 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();
@@ -1866,50 +1800,71 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.isIteratingOverLevels = true; // Paper - Throw exception on world create while being ticked
Iterator iterator = this.getAllLevels().iterator(); // Paper - Throw exception on world create while being ticked; move down
- while (iterator.hasNext()) {
- ServerLevel worldserver = (ServerLevel) iterator.next();
- worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
- worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
- net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers
- worldserver.updateLagCompensationTick(); // Paper - lag compensation
+ // SparklyPaper start - parallel world ticking
+ java.util.ArrayDeque<java.util.concurrent.Future<ServerLevel>> tasks = new java.util.ArrayDeque<>();
+ try {
+ while (iterator.hasNext()) {
+ ServerLevel worldserver = (ServerLevel) iterator.next();
+ worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
+ worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
+ net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers
+ worldserver.updateLagCompensationTick(); // Paper - lag compensation
- gameprofilerfiller.push(() -> {
- String s = String.valueOf(worldserver);
+ gameprofilerfiller.push(() -> {
+ String s = String.valueOf(worldserver);
- return s + " " + String.valueOf(worldserver.dimension().location());
- });
- /* Drop global time updates
- if (this.tickCount % 20 == 0) {
- gameprofilerfiller.push("timeSync");
- this.synchronizeTime(worldserver);
+ return s + " " + String.valueOf(worldserver.dimension().location());
+ });
+ /* Drop global time updates
+ if (this.tickCount % 20 == 0) {
+ gameprofilerfiller.push("timeSync");
+ this.synchronizeTime(worldserver);
+ gameprofilerfiller.pop();
+ }
+ // CraftBukkit end */
+
+ gameprofilerfiller.push("tick");
+
+ serverLevelTickingSemaphore.acquire();
+ tasks.add(
+ worldserver.tickExecutor.submit(() -> {
+ try {
+ ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread currentThread = (ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread) Thread.currentThread();
+ currentThread.currentlyTickingServerLevel = worldserver;
+
+ 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
+ } catch (Throwable throwable) {
+ CrashReport crashreport = CrashReport.forThrowable(throwable, "Exception ticking world");
+
+ worldserver.fillReportDetails(crashreport);
+ throw new ReportedException(crashreport);
+ } finally {
+ serverLevelTickingSemaphore.release();
+ }
+ }, worldserver)
+ );
+
+ gameprofilerfiller.pop();
gameprofilerfiller.pop();
+ worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions
}
- // CraftBukkit end */
-
- gameprofilerfiller.push("tick");
- try {
- 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
- } catch (Throwable throwable) {
- CrashReport crashreport = CrashReport.forThrowable(throwable, "Exception ticking world");
-
- worldserver.fillReportDetails(crashreport);
- throw new ReportedException(crashreport);
+ while (!tasks.isEmpty()) {
+ tasks.pop().get();
}
-
- gameprofilerfiller.pop();
- gameprofilerfiller.pop();
- worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions
+ } catch (java.lang.InterruptedException | java.util.concurrent.ExecutionException e) {
+ throw new RuntimeException(e); // Propagate exception
}
+ // SparklyPaper end
this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
gameprofilerfiller.popPush("connection");
@@ -2003,6 +1958,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
Map<ResourceKey<Level>, ServerLevel> oldLevels = this.levels;
Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
newLevels.remove(level.dimension());
+ level.tickExecutor.shutdown(); // SparklyPaper - parallel world ticking (We remove it in here instead of ServerLevel.close() because ServerLevel.close() is never called!)
this.levels = Collections.unmodifiableMap(newLevels);
}
// CraftBukkit end
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index e49e8e98ebbd3c8ba8a784f1626132699209b4e3..d952d42b8faee38f9d55fe578c0308916d31680c 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -243,6 +243,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
// SparklyPaper start - Spooky month optimizations
halloweenManager.startHalloweenEpochTask();
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index ba899bcf05b967db784b7435895d6ff7b72b40f2..f750d015bf8ffb533713f298a9d1df13de60693e 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -200,7 +200,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
private final MinecraftServer server;
public final PrimaryLevelData serverLevelData; // CraftBukkit - type
private int lastSpawnChunkRadius;
- final EntityTickList entityTickList = new EntityTickList();
+ final EntityTickList entityTickList = new EntityTickList(this);
// Paper - rewrite chunk system
private final GameEventDispatcher gameEventDispatcher;
public boolean noSave;
@@ -224,6 +224,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
private final StructureCheck structureCheck;
private final boolean tickTime;
private final RandomSequences randomSequences;
+ public java.util.concurrent.ExecutorService tickExecutor; // SparklyPaper - parallel world ticking
// CraftBukkit start
public final LevelStorageSource.LevelStorageAccess convertable;
@@ -682,6 +683,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
this.chunkDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController((ServerLevel)(Object)this, this.chunkTaskScheduler);
// Paper end - rewrite chunk system
this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
+ this.tickExecutor = java.util.concurrent.Executors.newSingleThreadExecutor(new net.sparklypower.sparklypaper.ServerLevelTickExecutorThreadFactory(getWorld().getName())); // SparklyPaper - parallel world ticking
}
// Paper start
@@ -1251,7 +1253,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
}
// Paper start - rewrite chunk system
if ((++this.tickedBlocksOrFluids & 7L) != 0L) {
- ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks();
+ // ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper
}
// Paper end - rewrite chunk system
@@ -1265,7 +1267,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
}
// Paper start - rewrite chunk system
if ((++this.tickedBlocksOrFluids & 7L) != 0L) {
- ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks();
+ // ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks) // Paper
}
// Paper end - rewrite chunk system
@@ -1558,6 +1560,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
}
private void addPlayer(ServerPlayer player) {
+ ca.spottedleaf.moonrise.common.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) {
@@ -1571,7 +1574,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
// CraftBukkit start
private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) {
- org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add entity off-main"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
entity.generation = false; // Paper - Don't fire sync event during generation; Reset flag if it was added during a ServerLevel generation process
// Paper start - extra debug info
if (entity.valid) {
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index 5a8f396d47577f087abb415c972fd4f51e50faba..f58331b6be3a26e76082d16894d3eb4d5982f318 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -359,6 +359,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple
return this.viewDistanceHolder;
}
// Paper end - rewrite chunk system
+ 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);
@@ -986,6 +987,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple
@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;
@@ -1629,6 +1631,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple
teleportTarget.postTeleportTransition().onTransition(this);
return this;
} else {
+ ca.spottedleaf.moonrise.common.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)
// CraftBukkit start
/*
this.isChangingDimension = true;
@@ -1996,6 +1999,12 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple
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 - Add titleOverride to InventoryOpenEvent
// CraftBukkit end
@@ -2059,6 +2068,11 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple
}
@Override
public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
+ // SparklyPaper start - parallel world ticking (debugging)
+ if (net.sparklypower.sparklypaper.configs.SparklyPaperConfigUtils.INSTANCE.getLogContainerCreationStacktraces()) {
+ MinecraftServer.LOGGER.warn("Closing " + this.getBukkitEntity().getName() + " inventory that was created at", this.containerMenu.containerCreationStacktrace);
+ }
+ // SparklyPaper end
CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit
// Paper end - Inventory close reason
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 1a956249828156fdc273888de59128c3d1a0b898..6484a87853eb2a907f729365edadc39e87e395bb 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -137,7 +137,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;
@@ -158,7 +158,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 - Configurable player collision
public PlayerList(MinecraftServer server, LayeredRegistryAccess<RegistryLayer> registryManager, PlayerDataStorage saveHandler, int maxPlayers) {
@@ -182,6 +182,7 @@ public abstract class PlayerList {
abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor
public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) {
+ ca.spottedleaf.moonrise.common.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 - Replace OfflinePlayer#getLastPlayed
GameProfile gameprofile = player.getGameProfile();
@@ -769,6 +770,13 @@ public abstract class PlayerList {
}
public ServerPlayer respawn(ServerPlayer entityplayer, boolean flag, Entity.RemovalReason entity_removalreason, RespawnReason reason, Location location) {
+ System.out.println("respawning player - current player container is " + entityplayer.containerMenu + " but their inventory is " + entityplayer.inventoryMenu);
+ // SparklyPaper - parallel world ticking (additional concurrency issues logs)
+ if (location != null)
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, from world " + entityplayer.serverLevel().getWorld().getName() + " to world " + location.getWorld().getName());
+ else
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, respawning in world " + entityplayer.serverLevel().getWorld().getName());
+ // SparklyPaper end
entityplayer.stopRiding(); // CraftBukkit
this.players.remove(entityplayer);
this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot
@@ -781,6 +789,7 @@ public abstract class PlayerList {
ServerPlayer entityplayer1 = entityplayer;
Level fromWorld = entityplayer.level();
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 a15546e433ebba6c0de01bdaaef201a3d99a87b5..3e440c7ce88d7ebd50eba488c629bfa0bd0d242f 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -882,7 +882,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
// CraftBukkit start
public void postTick() {
// No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle
- if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities
+ if (false && !(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities // SparklyPaper - parallel world ticking (see issue #9, this is executed in the server level tick for non-player entities)
this.handlePortal();
}
}
@@ -3986,6 +3986,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
private Entity teleportCrossDimension(ServerLevel world, TeleportTransition teleportTarget) {
+ ca.spottedleaf.moonrise.common.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)
List<Entity> list = this.getPassengers();
List<Entity> list1 = new ArrayList(list.size());
diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java
index 4680f77a275d8d2b226018db89a571ac25998dd8..42b00ada65878545e464aa169698288982d46def 100644
--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java
+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java
@@ -106,8 +106,14 @@ public abstract class AbstractContainerMenu {
this.title = title;
}
// CraftBukkit end
+ public Throwable containerCreationStacktrace; // SparklyPaper - parallel world ticking (debugging)
protected AbstractContainerMenu(@Nullable MenuType<?> type, int syncId) {
+ // SparklyPaper - parallel world ticking (debugging)
+ if (net.sparklypower.sparklypaper.configs.SparklyPaperConfigUtils.INSTANCE.getLogContainerCreationStacktraces()) {
+ this.containerCreationStacktrace = new Throwable();
+ }
+ // SparklyPaper end
this.carried = ItemStack.EMPTY;
this.remoteSlots = NonNullList.create();
this.remoteDataSlots = new IntArrayList();
diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java
index 33e7d2884195677c4d6340d8b84c1dd85c636ec1..02a719aaf7c860efbb51aed7d21cc83e14aef602 100644
--- a/src/main/java/net/minecraft/world/item/ItemStack.java
+++ b/src/main/java/net/minecraft/world/item/ItemStack.java
@@ -445,8 +445,8 @@ public final class ItemStack implements DataComponentHolder {
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/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 39d5633ae7cdb8609f5840de5ba26237d501d8a2..856aab98edbd67c68d33e529d0199c0506b11da8 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -175,6 +175,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray
public net.sparklypower.sparklypaper.configs.SparklyPaperConfig.SparklyPaperWorldConfig sparklyPaperConfig; // SparklyPaper
+ public com.destroystokyo.paper.util.RedstoneWireTurbo turbo = new com.destroystokyo.paper.util.RedstoneWireTurbo((net.minecraft.world.level.block.RedStoneWireBlock) net.minecraft.world.level.block.Blocks.REDSTONE_WIRE); // SparklyPaper - parallel world ticking (moved to world)
public static BlockPos lastPhysicsProblem; // Spigot
private org.spigotmc.TickLimiter entityLimiter;
private org.spigotmc.TickLimiter tileLimiter;
@@ -1088,6 +1089,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@Override
public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) {
+ ca.spottedleaf.moonrise.common.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 - Protect Bedrock and End Portal/Frames from being destroyed
@@ -1487,7 +1489,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
tickingblockentity.tick();
// Paper start - rewrite chunk system
if ((++tickedEntities & 7) == 0) {
- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks();
+ // ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks(); // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks)
}
// Paper end - rewrite chunk system
} // SparklyPaper end
@@ -1511,7 +1513,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
// Paper end - Prevent block entity and entity crashes
}
- this.moonrise$midTickTasks(); // Paper - rewrite chunk system
+ // this.moonrise$midTickTasks(); // Paper - rewrite chunk system // SparklyPaper - parallel world ticking (only run mid-tick at the end of each tick / fixes concurrency bugs related to executeMidTickTasks)
}
// Paper start - Option to prevent armor stands from doing entity lookups
@Override
@@ -1569,6 +1571,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@Nullable
public BlockEntity getBlockEntity(BlockPos blockposition, boolean validate) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThreadOrAsyncThread((ServerLevel) this, "Cannot read world asynchronously"); // SparklyPaper start - parallel world ticking
// Paper start - Perf: Optimize capturedTileEntities lookup
net.minecraft.world.level.block.entity.BlockEntity blockEntity;
if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(blockposition)) != null) {
@@ -1580,6 +1583,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
}
public void setBlockEntity(BlockEntity blockEntity) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel) this, "Cannot modify world asynchronously"); // SparklyPaper start - parallel world ticking
BlockPos blockposition = blockEntity.getBlockPos();
if (!this.isOutsideBuildHeight(blockposition)) {
@@ -1665,6 +1669,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
@Override
public List<Entity> getEntities(@Nullable Entity except, AABB box, Predicate<? super Entity> predicate) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, box, "Cannot getEntities asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
Profiler.get().incrementCounter("getEntities");
List<Entity> list = Lists.newArrayList();
@@ -1991,8 +1996,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
public abstract RecipeAccess recipeAccess();
public BlockPos getBlockRandomPos(int x, int y, int z, int l) {
- this.randValue = this.randValue * 3 + 1013904223;
- int i1 = this.randValue >> 2;
+ int i1 = this.random.nextInt() >> 2; // SparklyPaper - parallel world ticking
return new BlockPos(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15));
}
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 5a6c153fa2873aecba0d0d02be2cc2a514f445e3..1f8f05c02072cdfc69be0b2d7d33fd9d363a9539 100644
--- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java
@@ -52,7 +52,8 @@ public class DispenserBlock extends BaseEntityBlock {
private static final DefaultDispenseItemBehavior DEFAULT_BEHAVIOR = new DefaultDispenseItemBehavior();
public static final Map<Item, DispenseItemBehavior> DISPENSER_REGISTRY = new IdentityHashMap();
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
@Override
public MapCodec<? extends DispenserBlock> codec() {
@@ -109,7 +110,7 @@ public class DispenserBlock extends BaseEntityBlock {
if (idispensebehavior != DispenseItemBehavior.NOOP) {
if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - Add BlockPreDispenseEvent
- 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 ba5fff8892593a921f57cd55ad60cd6744ab4cf1..21b11b120a240ac39ef5d18a3401cb268dbfe6b1 100644
--- a/src/main/java/net/minecraft/world/level/block/FungusBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/FungusBlock.java
@@ -76,9 +76,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/MushroomBlock.java b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java
index 7900856c9e86afe907c00c9ac31bec93ece50abe..4e6adc7a8fd97613cb75d40fa38d504494bbe53b 100644
--- a/src/main/java/net/minecraft/world/level/block/MushroomBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/MushroomBlock.java
@@ -105,7 +105,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
+ SaplingBlock.treeTypeRT.set((this == Blocks.BROWN_MUSHROOM) ? TreeType.BROWN_MUSHROOM : TreeType.RED_MUSHROOM); // CraftBukkit // 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/RedStoneWireBlock.java b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
index 21f2c61023fadcce30452a02f067cd5d87e5d8dc..056bd2cb7898f691940cdcb85d124948ac49c7d1 100644
--- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java
@@ -292,7 +292,7 @@ public class RedStoneWireBlock extends Block {
// Paper start - Optimize redstone (Eigencraft)
// The bulk of the new functionality is found in RedstoneWireTurbo.java
- com.destroystokyo.paper.util.RedstoneWireTurbo turbo = new com.destroystokyo.paper.util.RedstoneWireTurbo(this);
+ // com.destroystokyo.paper.util.RedstoneWireTurbo turbo = new com.destroystokyo.paper.util.RedstoneWireTurbo(this); // SparklyPaper - parallel world ticking (moved to world)
/*
* Modified version of pre-existing updateSurroundingRedstone, which is called from
@@ -308,7 +308,7 @@ public class RedStoneWireBlock extends Block {
if (orientation != null) {
source = pos.relative(orientation.getFront().getOpposite());
}
- turbo.updateSurroundingRedstone(worldIn, pos, state, source);
+ worldIn.turbo.updateSurroundingRedstone(worldIn, pos, state, source); // turbo.updateSurroundingRedstone(worldIn, pos, state, source); // SparklyPaper - parallel world ticking
return;
}
updatePowerStrength(worldIn, pos, state, orientation, blockAdded);
@@ -336,7 +336,7 @@ public class RedStoneWireBlock extends Block {
// [Space Walker] suppress shape updates and emit those manually to
// bypass the new neighbor update stack.
if (level.setBlock(pos, state, Block.UPDATE_KNOWN_SHAPE | Block.UPDATE_CLIENTS)) {
- turbo.updateNeighborShapes(level, pos, state);
+ level.turbo.updateNeighborShapes(level, pos, state);
}
}
}
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 d262a5a6da57ef9ba9a6fe0dfbc88f577105e74f..15db3fb971a944e856b3cde86035d290673c0c1a 100644
--- a/src/main/java/net/minecraft/world/level/block/SaplingBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/SaplingBlock.java
@@ -35,7 +35,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);
protected final TreeGrower treeGrower;
- public static TreeType treeType; // CraftBukkit
+ public static final ThreadLocal<TreeType> treeTypeRT = new ThreadLocal<>(); // CraftBukkit // SparklyPaper - parallel world ticking (from Folia)
@Override
public MapCodec<? extends SaplingBlock> codec() {
@@ -73,8 +73,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/entity/BaseContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
index 1f29b2419914ca9257db6553f01b7e7ec49bfc18..8297c5be8c35fc6762c433aa072a78bf5fd666da 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
@@ -82,6 +82,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/SculkCatalystBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
index 4729befa12732a9fd65cce243b33b3b479026c41..a8113ee6b5a2e74182ba973231912ca636db5039 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
@@ -46,9 +46,9 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi
// Paper end - Fix NPE in SculkBloomEvent world access
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/TreeGrower.java b/src/main/java/net/minecraft/world/level/block/grower/TreeGrower.java
index 2fec480b6ca12a95013d920852db58722579caf1..5566c6a2992ac3aa0a9f9cd62f87d401c0a73179 100644
--- a/src/main/java/net/minecraft/world/level/block/grower/TreeGrower.java
+++ b/src/main/java/net/minecraft/world/level/block/grower/TreeGrower.java
@@ -175,55 +175,57 @@ public final class TreeGrower {
// CraftBukkit start
private 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_PINE;
+ treeType = TreeType.MEGA_PINE;
} 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 if (worldgentreeabstract == TreeFeatures.PALE_OAK) {
- SaplingBlock.treeType = TreeType.PALE_OAK;
+ treeType = TreeType.PALE_OAK;
} else if (worldgentreeabstract == TreeFeatures.PALE_OAK_CREAKING) {
- SaplingBlock.treeType = TreeType.PALE_OAK_CREAKING;
+ treeType = TreeType.PALE_OAK_CREAKING;
} 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 b4c16fe7c9215b5610b7e7488c29b497b5357ecc..9771f4ff6083868442fa142ef5c6c9a31397407f 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -382,6 +382,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
@Nullable
public BlockState setBlockState(BlockPos blockposition, BlockState iblockdata, boolean flag, boolean doPlace) {
+ ca.spottedleaf.moonrise.common.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 d8b4196adf955f8d414688dc451caac2d9c609d9..56089483da1313d2da90c1921af33c2d81024680 100644
--- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
@@ -10,17 +10,26 @@ import net.minecraft.world.entity.Entity;
public class EntityTickList {
private final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<net.minecraft.world.entity.Entity> entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system
+ // 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 - rewrite chunk system
}
public void add(Entity entity) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist addition"); // Paper // SparklyPaper - parallel world ticking (additional concurrency issues logs)
this.ensureActiveIsNotIterated();
this.entities.add(entity); // Paper - rewrite chunk system
}
public void remove(Entity entity) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist removal"); // Paper // SparklyPaper - parallel world ticking (additional concurrency issues logs)
this.ensureActiveIsNotIterated();
this.entities.remove(entity); // Paper - rewrite chunk system
}
@@ -30,7 +39,7 @@ public class EntityTickList {
}
public void forEach(Consumer<Entity> action) {
- // Paper start - rewrite chunk system
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverLevel, "Asynchronous entity ticklist iteration"); // Paper // SparklyPaper - parallel world ticking (additional concurrency issues logs)
// 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)
final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet.Iterator<Entity> iterator = this.entities.iterator();
diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapIndex.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapIndex.java
index 84f4664950e0cef7bd823bfc74f37cefce620d9e..b0c3b64da7aed676a5614c442948bbe96c3d2d73 100644
--- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapIndex.java
+++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapIndex.java
@@ -34,17 +34,21 @@ public class MapIndex extends SavedData {
@Override
public CompoundTag save(CompoundTag nbt, HolderLookup.Provider registries) {
+ synchronized (this.usedAuxIds) { // SparklyPaper start - make map data thread-safe
for (Entry<String> entry : this.usedAuxIds.object2IntEntrySet()) {
- nbt.putInt(entry.getKey(), entry.getIntValue());
+ nbt.putInt(entry.getKey(), entry.getIntValue());
}
+ } // SparklyPaper end - make map data thread-safe
return nbt;
}
public MapId getFreeAuxValueForMap() {
+ synchronized (this.usedAuxIds) { // SparklyPaper start - make map data thread-safe
int i = this.usedAuxIds.getInt("map") + 1;
this.usedAuxIds.put("map", i);
this.setDirty();
return new MapId(i);
+ } // SparklyPaper end - make map data thread-safe
}
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 57da11c0da7322e74810e7108e9c8000b0c36520..71709c8a8c5cec6403ec92badde412427c8f276b 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -459,7 +459,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
}
private boolean unloadChunk0(int x, int z, boolean save) {
- org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot
+ ca.spottedleaf.moonrise.common.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;
}
@@ -482,6 +482,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
if (!unloadChunk0(x, z, false)) {
return false;
}
+ ca.spottedleaf.moonrise.common.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
final long chunkKey = ChunkCoordIntPair.pair(x, z);
@@ -503,6 +504,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public boolean refreshChunk(int x, int z) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot refresh chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
if (playerChunk == null) return false;
@@ -563,7 +565,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
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.getHandle(), x, z, "May not sync load chunks asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
warnUnsafeChunk("loading a faraway chunk", x, z); // Paper
ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper
@@ -815,6 +817,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, loc.getX(), loc.getZ(), "Cannot generate tree asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
this.world.captureTreeGeneration = true;
this.world.captureBlockStates = true;
boolean grownTree = this.generateTree(loc, type);
@@ -930,6 +933,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
}
public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source, Consumer<net.minecraft.world.level.ServerExplosion> configurator) {
// Paper end - expand explosion API
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x, z, "Cannot create explosion asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
net.minecraft.world.level.Level.ExplosionInteraction explosionType;
if (!breakBlocks) {
explosionType = net.minecraft.world.level.Level.ExplosionInteraction.NONE; // Don't break blocks
@@ -1021,6 +1025,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, x >> 4, z >> 4, "Cannot retrieve chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper
// Transient load for this tick
return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z);
@@ -1051,6 +1056,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);
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, pos, "Cannot retrieve chunk asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
if (this.world.hasChunkAt(pos)) {
net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkAt(pos);
@@ -2327,6 +2333,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.world, position.getX(), position.getZ(), "Cannot send game event asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
getHandle().gameEvent(sourceEntity != null ? ((CraftEntity) sourceEntity).getHandle(): null, net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.get(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(gameEvent.getKey())).orElseThrow(), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position));
}
// Paper end
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
index 5cb69d0b822e11a99a96aef4f59986d083b079f4..78f9c3f3b068f638570942961ad41969abc9c028 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
@@ -75,6 +75,11 @@ public class CraftBlock implements Block {
}
public net.minecraft.world.level.block.state.BlockState getNMS() {
+ // Folia start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot read world asynchronously");
+ }
+ // Folia end - parallel world ticking
return this.world.getBlockState(this.position);
}
@@ -157,6 +162,11 @@ public class CraftBlock implements Block {
}
private void setData(final byte data, int flag) {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
this.world.setBlock(this.position, CraftMagicNumbers.getBlock(this.getType(), data), flag);
}
@@ -198,6 +208,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) {
+ ca.spottedleaf.moonrise.common.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
@@ -343,18 +359,33 @@ public class CraftBlock implements Block {
@Override
public Biome getBiome() {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ ca.spottedleaf.moonrise.common.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) {
+ ca.spottedleaf.moonrise.common.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) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio);
}
@@ -402,6 +433,11 @@ public class CraftBlock implements Block {
@Override
public boolean isBlockFaceIndirectlyPowered(BlockFace face) {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ ca.spottedleaf.moonrise.common.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);
@@ -414,6 +450,11 @@ public class CraftBlock implements Block {
@Override
public int getBlockPower(BlockFace face) {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ ca.spottedleaf.moonrise.common.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();
@@ -484,6 +525,11 @@ public class CraftBlock implements Block {
@Override
public boolean breakNaturally() {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
return this.breakNaturally(null);
}
@@ -543,6 +589,11 @@ public class CraftBlock implements Block {
@Override
public boolean applyBoneMeal(BlockFace face) {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ ca.spottedleaf.moonrise.common.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();
@@ -554,8 +605,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;
@@ -644,6 +695,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) {
+ ca.spottedleaf.moonrise.common.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();
@@ -685,6 +741,11 @@ public class CraftBlock implements Block {
@Override
public boolean canPlace(BlockData data) {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ ca.spottedleaf.moonrise.common.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();
@@ -719,6 +780,11 @@ public class CraftBlock implements Block {
@Override
public void tick() {
+ // SparklyPaper start - parallel world ticking
+ if (world instanceof ServerLevel serverWorld) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
final ServerLevel level = this.world.getMinecraftWorld();
this.getNMS().tick(level, this.position, level.random);
}
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
index 04ae258a2f8e98421340d29d5cceedec045171b7..3e90f16d303a8f3d1e2b01e7cbd62cbd991d3f06 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
@@ -25,7 +25,7 @@ public abstract class CraftBlockEntityState<T extends BlockEntity> extends Craft
private final T tileEntity;
private final T snapshot;
public boolean snapshotDisabled; // Paper
- public static boolean DISABLE_SNAPSHOT = false; // Paper
+ public static ThreadLocal<Boolean> DISABLE_SNAPSHOT = ThreadLocal.withInitial(() -> Boolean.FALSE); // SparklyPaper - parallel world ticking
public CraftBlockEntityState(World world, T tileEntity) {
super(world, tileEntity.getBlockPos(), tileEntity.getBlockState());
@@ -34,8 +34,8 @@ public abstract class CraftBlockEntityState<T extends BlockEntity> extends Craft
try { // Paper - Show blockstate location if we failed to read it
// Paper start
- this.snapshotDisabled = DISABLE_SNAPSHOT;
- if (DISABLE_SNAPSHOT) {
+ this.snapshotDisabled = DISABLE_SNAPSHOT.get(); // SparklyPaper - parallel world ticking
+ if (DISABLE_SNAPSHOT.get()) { // SparklyPaper - parallel world ticking
this.snapshot = this.tileEntity;
} else {
this.snapshot = this.createSnapshot(tileEntity);
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
index fa63a6cfcfcc4eee4503a82d85333c139c8c8b2b..9856860ee2987738bbcad5d752670e30f569ba74 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
@@ -215,6 +215,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) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously");
+ }
+ // SparklyPaper end - parallel world ticking
+
if (block.getType() != this.getType()) {
if (!force) {
return false;
@@ -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) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(world.getHandle(), position, "Cannot modify world asynchronously"); // SparklyPaper - parallel world ticking
this.requirePlaced();
net.minecraft.world.item.ItemStack nms = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(item);
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java
index 56453454cbd4b9e9270fc833f8ab38d5fa7a3763..ad507c36d89ce8639fbedbb1df0da66bdc96affa 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java
@@ -249,8 +249,8 @@ public final class CraftBlockStates {
net.minecraft.world.level.block.state.BlockState blockData = craftBlock.getNMS();
BlockEntity tileEntity = craftBlock.getHandle().getBlockEntity(blockPosition);
// Paper start - block state snapshots
- boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT;
- CraftBlockEntityState.DISABLE_SNAPSHOT = !useSnapshot;
+ boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT.get(); // SparklyPaper - parallel world ticking
+ CraftBlockEntityState.DISABLE_SNAPSHOT.set(!useSnapshot); // SparklyPaper - parallel world ticking
try {
// Paper end
CraftBlockState blockState = CraftBlockStates.getBlockState(world, blockPosition, blockData, tileEntity);
@@ -258,7 +258,7 @@ public final class CraftBlockStates {
return blockState;
// Paper start
} finally {
- CraftBlockEntityState.DISABLE_SNAPSHOT = prev;
+ CraftBlockEntityState.DISABLE_SNAPSHOT.set(prev); // SparklyPaper - parallel world ticking
}
// Paper end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
index 85926a55066ac793b2e4cfe3502f9ab201df91a3..122bcd352c01ba1a635c34a148bab25548301ee6 100644
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
@@ -951,7 +951,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
@@ -963,7 +963,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()) {
@@ -2247,7 +2247,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(), CraftVector.toBukkit(to));
- 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..9a40afbd7e5085179dc016c900ecb60e98250daf
--- /dev/null
+++ b/src/main/kotlin/net/sparklypower/sparklypaper/ServerLevelTickExecutorThreadFactory.kt
@@ -0,0 +1,20 @@
+package net.sparklypower.sparklypaper
+
+import ca.spottedleaf.moonrise.common.util.TickThread
+import java.util.concurrent.ThreadFactory
+
+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
diff --git a/src/main/kotlin/net/sparklypower/sparklypaper/configs/SparklyPaperConfigUtils.kt b/src/main/kotlin/net/sparklypower/sparklypaper/configs/SparklyPaperConfigUtils.kt
index 072b5811acf9ecc58e3bba7f74dc0dcf8556e848..ddb2fec6ff1604577705477237bdc6a7b2702328 100644
--- a/src/main/kotlin/net/sparklypower/sparklypaper/configs/SparklyPaperConfigUtils.kt
+++ b/src/main/kotlin/net/sparklypower/sparklypaper/configs/SparklyPaperConfigUtils.kt
@@ -23,6 +23,7 @@ object SparklyPaperConfigUtils {
)
lateinit var config: SparklyPaperConfig
+ val logContainerCreationStacktraces = java.lang.Boolean.getBoolean("sparklypaper.logContainerCreationStacktraces")
fun init(configFile: File) {
// Write default config if the file doesn't exist