9
0
mirror of https://github.com/SparklyPower/SparklyPaper.git synced 2025-12-24 01:19:30 +00:00
Files
SparklyPaperMC/patches/server/0024-Parallel-world-ticking.patch
MrPowerGamerBR 81aca647ae Add PlayerMoveControllableVehicleEvent
Useful if you want to actually control what the player is moving, because VehicleMoveEvent does not let you cancel nor change the location. Also useful to detect BoatFly hacks.
2025-01-05 00:02:48 -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