mirror of
https://github.com/SparklyPower/SparklyPaper.git
synced 2025-12-19 15:09:27 +00:00
Parallel World Ticking patches (unfinished)
This commit is contained in:
@@ -0,0 +1,706 @@
|
|||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: MrPowerGamerBR <git@mrpowergamerbr.com>
|
||||||
|
Date: Sun, 25 May 2025 21:39:32 -0300
|
||||||
|
Subject: [PATCH] Parallel World Ticking
|
||||||
|
|
||||||
|
|
||||||
|
diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
|
||||||
|
index f473999938840562b1007a789600342e5796a123..cb62ac5ee7ed059adde7027156421bdbef0924f3 100644
|
||||||
|
--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
|
||||||
|
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
|
||||||
|
@@ -1116,7 +1116,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) {
|
||||||
|
@@ -1142,7 +1142,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");
|
||||||
|
@@ -1424,7 +1424,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/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java
|
||||||
|
index ca02c4c71a0a5a1a0ae8bbb40f0b1b7eac64e6fd..7ce68f270d809ab0b2be45ffdd0346f053bd3b1e 100644
|
||||||
|
--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java
|
||||||
|
+++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java
|
||||||
|
@@ -401,8 +401,8 @@ public interface DispenseItemBehavior {
|
||||||
|
// CraftBukkit start
|
||||||
|
level.captureTreeGeneration = false;
|
||||||
|
if (!level.capturedBlockStates.isEmpty()) {
|
||||||
|
- org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType;
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = null;
|
||||||
|
+ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeTypeRT.get(); // SparklyPaper - parallel world ticking
|
||||||
|
+ net.minecraft.world.level.block.SaplingBlock.treeTypeRT.set(null); // SparklyPaper - parallel world ticking
|
||||||
|
org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(blockPos, level.getWorld());
|
||||||
|
List<org.bukkit.block.BlockState> states = new java.util.ArrayList<>(level.capturedBlockStates.values());
|
||||||
|
level.capturedBlockStates.clear();
|
||||||
|
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||||
|
index 6670762f64eb4f1904c1a83f55aae4fe638462d3..7485ce0c4ff7dd1d2b04cb162b2c2759752df98a 100644
|
||||||
|
--- a/net/minecraft/server/MinecraftServer.java
|
||||||
|
+++ b/net/minecraft/server/MinecraftServer.java
|
||||||
|
@@ -303,6 +303,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||||
|
private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
|
||||||
|
public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation
|
||||||
|
public final Set<net.minecraft.world.entity.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 java.util.concurrent.Semaphore serverLevelTickingSemaphore = null; // SparklyPaper - parallel world ticking
|
||||||
|
|
||||||
|
public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
|
||||||
|
AtomicReference<S> atomicReference = new AtomicReference<>();
|
||||||
|
@@ -1717,6 +1718,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isIteratingOverLevels = true; // Paper - Throw exception on world create while being ticked
|
||||||
|
+ // SparklyPaper start - parallel world ticking
|
||||||
|
+ java.util.ArrayDeque<java.util.concurrent.Future<ServerLevel>> tasks = new java.util.ArrayDeque<>();
|
||||||
|
+ try {
|
||||||
|
for (ServerLevel serverLevel : this.getAllLevels()) {
|
||||||
|
serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
|
||||||
|
serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
|
||||||
|
@@ -1733,27 +1737,46 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||||
|
|
||||||
|
profilerFiller.push("tick");
|
||||||
|
|
||||||
|
- try {
|
||||||
|
- long i = Util.getNanos(); // SparklyPaper - track world's MSPT
|
||||||
|
- serverLevel.tick(hasTimeLeft);
|
||||||
|
- // SparklyPaper start - track world's MSPT
|
||||||
|
- long j = Util.getNanos() - i;
|
||||||
|
-
|
||||||
|
- // These are from the "tickServer" function
|
||||||
|
- serverLevel.tickTimes5s.add(this.tickCount, j);
|
||||||
|
- serverLevel.tickTimes10s.add(this.tickCount, j);
|
||||||
|
- serverLevel.tickTimes60s.add(this.tickCount, j);
|
||||||
|
- // SparklyPaper end
|
||||||
|
- } catch (Throwable var7) {
|
||||||
|
- CrashReport crashReport = CrashReport.forThrowable(var7, "Exception ticking world");
|
||||||
|
- serverLevel.fillReportDetails(crashReport);
|
||||||
|
- throw new ReportedException(crashReport);
|
||||||
|
- }
|
||||||
|
+ serverLevelTickingSemaphore.acquire();
|
||||||
|
+ tasks.add(
|
||||||
|
+ serverLevel.tickExecutor.submit(() -> {
|
||||||
|
+ try {
|
||||||
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread currentThread = (ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread) Thread.currentThread();
|
||||||
|
+ currentThread.currentlyTickingServerLevel = serverLevel;
|
||||||
|
+
|
||||||
|
+ long i = Util.getNanos(); // SparklyPaper - track world's MSPT
|
||||||
|
+ serverLevel.tick(hasTimeLeft);
|
||||||
|
+ // SparklyPaper start - track world's MSPT
|
||||||
|
+ long j = Util.getNanos() - i;
|
||||||
|
+
|
||||||
|
+ // These are from the "tickServer" function
|
||||||
|
+ serverLevel.tickTimes5s.add(this.tickCount, j);
|
||||||
|
+ serverLevel.tickTimes10s.add(this.tickCount, j);
|
||||||
|
+ serverLevel.tickTimes60s.add(this.tickCount, j);
|
||||||
|
+ // SparklyPaper end
|
||||||
|
+ } catch (Throwable throwable) {
|
||||||
|
+ CrashReport crashreport = CrashReport.forThrowable(throwable, "Exception ticking world");
|
||||||
|
+
|
||||||
|
+ serverLevel.fillReportDetails(crashreport);
|
||||||
|
+ throw new ReportedException(crashreport);
|
||||||
|
+ } finally {
|
||||||
|
+ serverLevelTickingSemaphore.release();
|
||||||
|
+ }
|
||||||
|
+ }, serverLevel)
|
||||||
|
+ );
|
||||||
|
|
||||||
|
profilerFiller.pop();
|
||||||
|
profilerFiller.pop();
|
||||||
|
serverLevel.explosionDensityCache.clear(); // Paper - Optimize explosions
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ while (!tasks.isEmpty()) {
|
||||||
|
+ tasks.pop().get();
|
||||||
|
+ }
|
||||||
|
+ } catch (java.lang.InterruptedException | java.util.concurrent.ExecutionException e) {
|
||||||
|
+ throw new RuntimeException(e); // Propagate exception
|
||||||
|
+ }
|
||||||
|
+ // SparklyPaper end
|
||||||
|
this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
|
||||||
|
|
||||||
|
profilerFiller.popPush("connection");
|
||||||
|
@@ -1844,6 +1867,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/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
|
||||||
|
index 8e83d37ffda1b5bfcacd5578e622b5dc09f6870c..11d334db1688dfaf087d854da604895fe7091b8e 100644
|
||||||
|
--- a/net/minecraft/server/dedicated/DedicatedServer.java
|
||||||
|
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
|
||||||
|
@@ -193,6 +193,10 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
|
||||||
|
}
|
||||||
|
net.sparklypower.sparklypaper.SparklyPaperCommands.INSTANCE.registerCommands(this);
|
||||||
|
// SparklyPaper end
|
||||||
|
+ // SparklyPaper start - parallel world ticking start
|
||||||
|
+ serverLevelTickingSemaphore = new java.util.concurrent.Semaphore(net.sparklypower.sparklypaper.configs.SparklyPaperConfigUtils.INSTANCE.getConfig().getParallelWorldTicking().getThreads());
|
||||||
|
+ DedicatedServer.LOGGER.info("Using " + serverLevelTickingSemaphore.availablePermits() + " permits for parallel world ticking"); // SparklyPaper - parallel world ticking
|
||||||
|
+ // SparklyPaper end
|
||||||
|
|
||||||
|
this.setPvpAllowed(properties.pvp);
|
||||||
|
this.setFlightAllowed(properties.allowFlight);
|
||||||
|
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
|
||||||
|
index 2915ad139a094f6d487c65b39ef065c1340a4685..dac59bdd0496c592d8148e05b82e6ff280f05d5d 100644
|
||||||
|
--- a/net/minecraft/server/level/ServerLevel.java
|
||||||
|
+++ b/net/minecraft/server/level/ServerLevel.java
|
||||||
|
@@ -180,7 +180,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
|
||||||
|
private final MinecraftServer server;
|
||||||
|
public final net.minecraft.world.level.storage.PrimaryLevelData serverLevelData; // CraftBukkit - type
|
||||||
|
private int lastSpawnChunkRadius;
|
||||||
|
- final EntityTickList entityTickList = new EntityTickList();
|
||||||
|
+ final EntityTickList entityTickList = new EntityTickList(this); // SparklyPaper - parallel world ticking
|
||||||
|
// Paper - rewrite chunk system
|
||||||
|
private final GameEventDispatcher gameEventDispatcher;
|
||||||
|
public boolean noSave;
|
||||||
|
@@ -204,6 +204,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 levelStorageAccess;
|
||||||
|
@@ -677,6 +678,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
|
||||||
|
@@ -1225,7 +1227,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 end - rewrite chunk system
|
||||||
|
|
||||||
|
@@ -1238,7 +1240,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 end - rewrite chunk system
|
||||||
|
|
||||||
|
@@ -1498,6 +1500,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 = this.getEntity(player.getUUID());
|
||||||
|
if (entity != null) {
|
||||||
|
LOGGER.warn("Force-added player with duplicate UUID {}", player.getUUID());
|
||||||
|
@@ -1510,7 +1513,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
|
||||||
|
|
||||||
|
// CraftBukkit start
|
||||||
|
private boolean addEntity(Entity entity, @Nullable org.bukkit.event.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/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java
|
||||||
|
index 3781d9cc174b7aecacb9b9855d52c7b1ff05835c..1f762b49a2a8f6e469ce0d7336198d5f82737a0b 100644
|
||||||
|
--- a/net/minecraft/server/level/ServerPlayer.java
|
||||||
|
+++ b/net/minecraft/server/level/ServerPlayer.java
|
||||||
|
@@ -450,6 +450,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
|
||||||
|
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 level, GameProfile gameProfile, ClientInformation clientInformation) {
|
||||||
|
super(level, level.getSharedSpawnPos(), level.getSharedSpawnAngle(), gameProfile);
|
||||||
|
@@ -736,6 +737,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
|
||||||
|
|
||||||
|
@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;
|
||||||
|
@@ -1364,6 +1366,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
|
||||||
|
return this;
|
||||||
|
} else {
|
||||||
|
// CraftBukkit start
|
||||||
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot change dimension of a player off-main, from world " + serverLevel().getWorld().getName() + " to world " + level.getWorld().getName()); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
||||||
|
/*
|
||||||
|
this.isChangingDimension = true;
|
||||||
|
LevelData levelData = level.getLevelData();
|
||||||
|
@@ -1696,6 +1699,12 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
|
||||||
|
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 " + abstractContainerMenu + " because we haven't ticked in the current world yet!", new Throwable());
|
||||||
|
+ return OptionalInt.empty();
|
||||||
|
+ }
|
||||||
|
+ // SparklyPaper end
|
||||||
|
this.containerMenu = abstractContainerMenu; // Moved up
|
||||||
|
if (!this.isImmobile())
|
||||||
|
this.connection
|
||||||
|
@@ -1760,6 +1769,11 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
|
||||||
|
}
|
||||||
|
@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
|
||||||
|
org.bukkit.craftbukkit.event.CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit
|
||||||
|
// Paper end - Inventory close reason
|
||||||
|
this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId));
|
||||||
|
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
|
||||||
|
index 9ca3c55a3b5b1a532b86b08eb92460df4cb54f2a..b493183213e540c4170680cd1b7699348ae53621 100644
|
||||||
|
--- a/net/minecraft/server/players/PlayerList.java
|
||||||
|
+++ b/net/minecraft/server/players/PlayerList.java
|
||||||
|
@@ -113,7 +113,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 = new UserBanList(USERBANLIST_FILE);
|
||||||
|
private final IpBanList ipBans = new IpBanList(IPBANLIST_FILE);
|
||||||
|
private final ServerOpList ops = new ServerOpList(OPLIST_FILE);
|
||||||
|
@@ -134,7 +134,7 @@ public abstract class PlayerList {
|
||||||
|
|
||||||
|
// CraftBukkit start
|
||||||
|
private org.bukkit.craftbukkit.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> registries, PlayerDataStorage playerIo, int maxPlayers) {
|
||||||
|
@@ -150,6 +150,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 cookie) {
|
||||||
|
+ 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();
|
||||||
|
@@ -713,6 +714,12 @@ public abstract class PlayerList {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerPlayer respawn(ServerPlayer player, boolean keepInventory, Entity.RemovalReason reason, @Nullable org.bukkit.event.player.PlayerRespawnEvent.RespawnReason eventReason, @Nullable org.bukkit.Location location) {
|
||||||
|
+ // SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
||||||
|
+ if (location != null) // TODO: Is this really never null, or is IntelliJ IDEA tripping? Because I'm pretty sure that this can be null and there isn't any @NotNull annotations
|
||||||
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, from world " + player.serverLevel().getWorld().getName() + " to world " + location.getWorld().getName());
|
||||||
|
+ else
|
||||||
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, respawning in world " + player.serverLevel().getWorld().getName());
|
||||||
|
+ // SparklyPaper end
|
||||||
|
player.stopRiding(); // CraftBukkit
|
||||||
|
this.players.remove(player);
|
||||||
|
this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot
|
||||||
|
@@ -723,6 +730,7 @@ public abstract class PlayerList {
|
||||||
|
ServerPlayer serverPlayer = player;
|
||||||
|
Level fromWorld = player.level();
|
||||||
|
player.wonGame = false;
|
||||||
|
+ serverPlayer.hasTickedAtLeastOnceInNewWorld = false; // SparklyPaper - parallel world ticking (see: PARALLEL_NOTES.md - Opening an inventory after a world switch)
|
||||||
|
// CraftBukkit end
|
||||||
|
serverPlayer.connection = player.connection;
|
||||||
|
serverPlayer.restoreFrom(player, keepInventory);
|
||||||
|
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
|
||||||
|
index 81a18b8e605bd4c28b48a32c80be231609182970..bdb0e6b868101c1204b93c13623d2f55c96e64c8 100644
|
||||||
|
--- a/net/minecraft/world/entity/Entity.java
|
||||||
|
+++ b/net/minecraft/world/entity/Entity.java
|
||||||
|
@@ -805,7 +805,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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -3788,6 +3788,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
|
||||||
|
}
|
||||||
|
|
||||||
|
private Entity teleportCrossDimension(ServerLevel level, TeleportTransition teleportTransition) {
|
||||||
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(level, "Cannot teleport entity to another world off-main, from world " + this.level.getWorld().getName() + " to world " + level.getWorld().getName()); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
||||||
|
List<Entity> passengers = this.getPassengers();
|
||||||
|
List<Entity> list = new ArrayList<>(passengers.size());
|
||||||
|
this.ejectPassengers();
|
||||||
|
diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java
|
||||||
|
index 813417a09b4acc7d57e80a53d970767e230d75b1..b8ff46e7543f00d963a6aa87509a1173c0ee34ec 100644
|
||||||
|
--- a/net/minecraft/world/inventory/AbstractContainerMenu.java
|
||||||
|
+++ b/net/minecraft/world/inventory/AbstractContainerMenu.java
|
||||||
|
@@ -95,8 +95,14 @@ public abstract class AbstractContainerMenu {
|
||||||
|
|
||||||
|
public void startOpen() {}
|
||||||
|
// CraftBukkit end
|
||||||
|
+ public Throwable containerCreationStacktrace; // SparklyPaper - parallel world ticking (debugging)
|
||||||
|
|
||||||
|
protected AbstractContainerMenu(@Nullable MenuType<?> menuType, int containerId) {
|
||||||
|
+ // SparklyPaper - parallel world ticking (debugging)
|
||||||
|
+ if (net.sparklypower.sparklypaper.configs.SparklyPaperConfigUtils.INSTANCE.getLogContainerCreationStacktraces()) {
|
||||||
|
+ this.containerCreationStacktrace = new Throwable();
|
||||||
|
+ }
|
||||||
|
+ // SparklyPaper end
|
||||||
|
this.menuType = menuType;
|
||||||
|
this.containerId = containerId;
|
||||||
|
}
|
||||||
|
diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java
|
||||||
|
index 24ecca78dc1140b6fc47d59f2acefca6bc2b0220..e02e149c4ea52f2c3e4670d81504b655ebd86026 100644
|
||||||
|
--- a/net/minecraft/world/item/ItemStack.java
|
||||||
|
+++ b/net/minecraft/world/item/ItemStack.java
|
||||||
|
@@ -406,8 +406,8 @@ public final class ItemStack implements DataComponentHolder {
|
||||||
|
if (interactionResult.consumesAction() && serverLevel.captureTreeGeneration && !serverLevel.capturedBlockStates.isEmpty()) {
|
||||||
|
serverLevel.captureTreeGeneration = false;
|
||||||
|
org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(clickedPos, serverLevel.getWorld());
|
||||||
|
- org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType;
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = null;
|
||||||
|
+ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeTypeRT.get(); // SparklyPaper - parallel world ticking
|
||||||
|
+ net.minecraft.world.level.block.SaplingBlock.treeTypeRT.set(null);
|
||||||
|
List<org.bukkit.craftbukkit.block.CraftBlockState> blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values());
|
||||||
|
serverLevel.capturedBlockStates.clear();
|
||||||
|
org.bukkit.event.world.StructureGrowEvent structureEvent = null;
|
||||||
|
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
|
||||||
|
index 71454f733a568e713d6be01a8c882529a0ef1b35..617d1b0d99c90a4f2e252a06cb531ed9ceaae9fb 100644
|
||||||
|
--- a/net/minecraft/world/level/Level.java
|
||||||
|
+++ b/net/minecraft/world/level/Level.java
|
||||||
|
@@ -161,6 +161,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
|
||||||
|
}
|
||||||
|
// Paper end - add paper world config
|
||||||
|
|
||||||
|
+ public io.papermc.paper.redstone.RedstoneWireTurbo turbo = new io.papermc.paper.redstone.RedstoneWireTurbo((net.minecraft.world.level.block.RedStoneWireBlock) net.minecraft.world.level.block.Blocks.REDSTONE_WIRE); // SparklyPaper - parallel world ticking (moved to world)
|
||||||
|
public static @Nullable BlockPos lastPhysicsProblem; // Spigot
|
||||||
|
private int tileTickPosition;
|
||||||
|
public final Map<ServerExplosion.CacheKey, Float> explosionDensityCache = new java.util.HashMap<>(); // Paper - Optimize explosions
|
||||||
|
@@ -1085,6 +1086,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft) {
|
||||||
|
+ 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
|
||||||
|
@@ -1466,7 +1468,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
|
||||||
|
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
|
||||||
|
}
|
||||||
|
@@ -1489,7 +1491,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
|
||||||
|
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
|
||||||
|
@@ -1632,6 +1634,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public BlockEntity getBlockEntity(BlockPos pos, boolean validate) {
|
||||||
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThreadOrAsyncThread((ServerLevel) this, "Cannot read world asynchronously"); // SparklyPaper - 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(pos)) != null) {
|
||||||
|
@@ -1649,6 +1652,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlockEntity(BlockEntity blockEntity) {
|
||||||
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel) this, "Cannot modify world asynchronously"); // SparklyPaper - parallel world ticking
|
||||||
|
BlockPos blockPos = blockEntity.getBlockPos();
|
||||||
|
if (!this.isOutsideBuildHeight(blockPos)) {
|
||||||
|
// CraftBukkit start
|
||||||
|
@@ -1733,6 +1737,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Entity> getEntities(@Nullable Entity entity, AABB boundingBox, Predicate<? super Entity> predicate) {
|
||||||
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
||||||
|
Profiler.get().incrementCounter("getEntities");
|
||||||
|
List<Entity> list = Lists.newArrayList();
|
||||||
|
|
||||||
|
@@ -2054,8 +2059,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
|
||||||
|
public abstract RecipeAccess recipeAccess();
|
||||||
|
|
||||||
|
public BlockPos getBlockRandomPos(int x, int y, int z, int yMask) {
|
||||||
|
- this.randValue = this.randValue * 3 + 1013904223;
|
||||||
|
- int i = this.randValue >> 2;
|
||||||
|
+ int i = this.random.nextInt() >> 2; // SparklyPaper - parallel world ticking
|
||||||
|
return new BlockPos(x + (i & 15), y + (i >> 16 & yMask), z + (i >> 8 & 15));
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/net/minecraft/world/level/block/FungusBlock.java b/net/minecraft/world/level/block/FungusBlock.java
|
||||||
|
index 9711efb088bd0da9168e9bcd0496bd7caddd2974..63da283d68c7e1184d1995aeb08cd8720f1b02ee 100644
|
||||||
|
--- a/net/minecraft/world/level/block/FungusBlock.java
|
||||||
|
+++ b/net/minecraft/world/level/block/FungusBlock.java
|
||||||
|
@@ -76,9 +76,9 @@ public class FungusBlock extends VegetationBlock implements BonemealableBlock {
|
||||||
|
// CraftBukkit start
|
||||||
|
.map((value) -> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
})
|
||||||
|
diff --git a/net/minecraft/world/level/block/MushroomBlock.java b/net/minecraft/world/level/block/MushroomBlock.java
|
||||||
|
index d306f5f524dc64618df94c9783c2168dc561a5e3..72363832a60a380869b60b1ec4a352a1e11608a8 100644
|
||||||
|
--- a/net/minecraft/world/level/block/MushroomBlock.java
|
||||||
|
+++ b/net/minecraft/world/level/block/MushroomBlock.java
|
||||||
|
@@ -93,7 +93,7 @@ public class MushroomBlock extends VegetationBlock implements BonemealableBlock
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
level.removeBlock(pos, false);
|
||||||
|
- SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM; // CraftBukkit
|
||||||
|
+ SaplingBlock.treeTypeRT.set((this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM); // CraftBukkit // SparklyPaper - parallel world ticking
|
||||||
|
if (optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
diff --git a/net/minecraft/world/level/block/RedStoneWireBlock.java b/net/minecraft/world/level/block/RedStoneWireBlock.java
|
||||||
|
index 1943a6aad888647953e2d9dbbeedb0bd81c6f9df..2c4c57deead027fc78904e7e9bc8a2e213225960 100644
|
||||||
|
--- a/net/minecraft/world/level/block/RedStoneWireBlock.java
|
||||||
|
+++ b/net/minecraft/world/level/block/RedStoneWireBlock.java
|
||||||
|
@@ -267,7 +267,7 @@ public class RedStoneWireBlock extends Block {
|
||||||
|
|
||||||
|
// Paper start - Optimize redstone (Eigencraft)
|
||||||
|
// The bulk of the new functionality is found in RedstoneWireTurbo.java
|
||||||
|
- io.papermc.paper.redstone.RedstoneWireTurbo turbo = new io.papermc.paper.redstone.RedstoneWireTurbo(this);
|
||||||
|
+ // io.papermc.paper.redstone.RedstoneWireTurbo turbo = new io.papermc.paper.redstone.RedstoneWireTurbo(this); // SparklyPaper - parallel world ticking (moved to world)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Modified version of pre-existing updateSurroundingRedstone, which is called from
|
||||||
|
@@ -283,7 +283,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); // SparklyPaper - parallel world ticking
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updatePowerStrength(worldIn, pos, state, orientation, blockAdded);
|
||||||
|
@@ -311,7 +311,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); // SparklyPaper - parallel world ticking
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff --git a/net/minecraft/world/level/block/SaplingBlock.java b/net/minecraft/world/level/block/SaplingBlock.java
|
||||||
|
index a22cb810622e0ae97bc2a0d6390d026d9482b783..7934299d633524a9ef8c9d315a48c0f5bbe13b4c 100644
|
||||||
|
--- a/net/minecraft/world/level/block/SaplingBlock.java
|
||||||
|
+++ b/net/minecraft/world/level/block/SaplingBlock.java
|
||||||
|
@@ -25,7 +25,7 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock {
|
||||||
|
public static final IntegerProperty STAGE = BlockStateProperties.STAGE;
|
||||||
|
private static final VoxelShape SHAPE = Block.column(12.0, 0.0, 12.0);
|
||||||
|
protected final TreeGrower treeGrower;
|
||||||
|
- public static @javax.annotation.Nullable org.bukkit.TreeType treeType; // CraftBukkit
|
||||||
|
+ public static final ThreadLocal<org.bukkit.TreeType> treeTypeRT = new ThreadLocal<>(); // SparklyPaper - parallel world ticking (from Folia)
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MapCodec<? extends SaplingBlock> codec() {
|
||||||
|
@@ -62,8 +62,8 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock {
|
||||||
|
this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random);
|
||||||
|
level.captureTreeGeneration = false;
|
||||||
|
if (!level.capturedBlockStates.isEmpty()) {
|
||||||
|
- org.bukkit.TreeType treeType = SaplingBlock.treeType;
|
||||||
|
- SaplingBlock.treeType = null;
|
||||||
|
+ org.bukkit.TreeType treeType = SaplingBlock.treeTypeRT.get(); // SparklyPaper - parallel world ticking
|
||||||
|
+ SaplingBlock.treeTypeRT.set(null);
|
||||||
|
org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level.getWorld());
|
||||||
|
java.util.List<org.bukkit.block.BlockState> blocks = new java.util.ArrayList<>(level.capturedBlockStates.values());
|
||||||
|
level.capturedBlockStates.clear();
|
||||||
|
diff --git a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
|
||||||
|
index c63370fd458fb4f7190b79b1a8174fcc92d88f9c..5c2033ef17d27ee7242de0c4a00425f74d50cad9 100644
|
||||||
|
--- a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
|
||||||
|
+++ b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
|
||||||
|
@@ -79,6 +79,12 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean canUnlock(Player player, LockCode code, Component displayName, @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(displayName));
|
||||||
|
diff --git a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
|
||||||
|
index 2627583ab12d886b1fba0b1d1e599f942926b499..2caa770411c542a70fe50267ce4cffb22fc94b97 100644
|
||||||
|
--- a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
|
||||||
|
+++ b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
|
||||||
|
@@ -43,9 +43,7 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi
|
||||||
|
// Paper end - Fix NPE in SculkBloomEvent world access
|
||||||
|
|
||||||
|
public static void serverTick(Level level, BlockPos pos, BlockState state, SculkCatalystBlockEntity sculkCatalyst) {
|
||||||
|
- org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = sculkCatalyst.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.
|
||||||
|
sculkCatalyst.catalystListener.getSculkSpreader().updateCursors(level, pos, level.getRandom(), true);
|
||||||
|
- org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = null; // CraftBukkit
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
diff --git a/net/minecraft/world/level/block/grower/TreeGrower.java b/net/minecraft/world/level/block/grower/TreeGrower.java
|
||||||
|
index d23f255de9208f42125fa358a9e8194c984fe4d3..54d64e77dc8c4bef25822d74a30be1a860cfec36 100644
|
||||||
|
--- a/net/minecraft/world/level/block/grower/TreeGrower.java
|
||||||
|
+++ b/net/minecraft/world/level/block/grower/TreeGrower.java
|
||||||
|
@@ -203,55 +203,57 @@ public final class TreeGrower {
|
||||||
|
|
||||||
|
// CraftBukkit start
|
||||||
|
private void setTreeType(Holder<ConfiguredFeature<?, ?>> feature) {
|
||||||
|
+ org.bukkit.TreeType treeType; // SparklyPaper - parallel world ticking
|
||||||
|
if (feature.is(TreeFeatures.OAK) || feature.is(TreeFeatures.OAK_BEES_005)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TREE;
|
||||||
|
+ treeType = org.bukkit.TreeType.TREE; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.HUGE_RED_MUSHROOM)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.RED_MUSHROOM;
|
||||||
|
+ treeType = org.bukkit.TreeType.RED_MUSHROOM; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.HUGE_BROWN_MUSHROOM)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BROWN_MUSHROOM;
|
||||||
|
+ treeType = org.bukkit.TreeType.BROWN_MUSHROOM; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.JUNGLE_TREE)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.COCOA_TREE;
|
||||||
|
+ treeType = org.bukkit.TreeType.COCOA_TREE; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.JUNGLE_TREE_NO_VINE)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SMALL_JUNGLE;
|
||||||
|
+ treeType = org.bukkit.TreeType.SMALL_JUNGLE; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.PINE)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_REDWOOD;
|
||||||
|
+ treeType = org.bukkit.TreeType.TALL_REDWOOD; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.SPRUCE)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.REDWOOD;
|
||||||
|
+ treeType = org.bukkit.TreeType.REDWOOD; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.ACACIA)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.ACACIA;
|
||||||
|
+ treeType = org.bukkit.TreeType.ACACIA; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.BIRCH) || feature.is(TreeFeatures.BIRCH_BEES_005)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIRCH;
|
||||||
|
+ treeType = org.bukkit.TreeType.BIRCH; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.SUPER_BIRCH_BEES_0002)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_BIRCH;
|
||||||
|
+ treeType = org.bukkit.TreeType.TALL_BIRCH; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.SWAMP_OAK)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SWAMP;
|
||||||
|
+ treeType = org.bukkit.TreeType.SWAMP; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.FANCY_OAK) || feature.is(TreeFeatures.FANCY_OAK_BEES_005)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIG_TREE;
|
||||||
|
+ treeType = org.bukkit.TreeType.BIG_TREE; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.JUNGLE_BUSH)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE_BUSH;
|
||||||
|
+ treeType = org.bukkit.TreeType.JUNGLE_BUSH; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.DARK_OAK)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.DARK_OAK;
|
||||||
|
+ treeType = org.bukkit.TreeType.DARK_OAK; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.MEGA_SPRUCE)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_REDWOOD;
|
||||||
|
+ treeType = org.bukkit.TreeType.MEGA_REDWOOD; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.MEGA_PINE)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_PINE;
|
||||||
|
+ treeType = org.bukkit.TreeType.MEGA_PINE; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.MEGA_JUNGLE_TREE)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE;
|
||||||
|
+ treeType = org.bukkit.TreeType.JUNGLE; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.AZALEA_TREE)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.AZALEA;
|
||||||
|
+ treeType = org.bukkit.TreeType.AZALEA; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.MANGROVE)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MANGROVE;
|
||||||
|
+ treeType = org.bukkit.TreeType.MANGROVE; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.TALL_MANGROVE)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_MANGROVE;
|
||||||
|
+ treeType = org.bukkit.TreeType.TALL_MANGROVE; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.CHERRY) || feature.is(TreeFeatures.CHERRY_BEES_005)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.CHERRY;
|
||||||
|
+ treeType = org.bukkit.TreeType.CHERRY; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.PALE_OAK) || feature.is(TreeFeatures.PALE_OAK_BONEMEAL)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK;
|
||||||
|
+ treeType = org.bukkit.TreeType.PALE_OAK; // SparklyPaper - parallel world ticking
|
||||||
|
} else if (feature.is(TreeFeatures.PALE_OAK_CREAKING)) {
|
||||||
|
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK_CREAKING;
|
||||||
|
+ treeType = org.bukkit.TreeType.PALE_OAK_CREAKING; // SparklyPaper - parallel world ticking
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unknown tree generator " + feature);
|
||||||
|
}
|
||||||
|
+ net.minecraft.world.level.block.SaplingBlock.treeTypeRT.set(treeType); // SparklyPaper - parallel world ticking
|
||||||
|
}
|
||||||
|
// CraftBukkit end
|
||||||
|
}
|
||||||
|
diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
|
||||||
|
index 5d3fc807221392d378fec283bfdefb8747fb8376..b674e2dbf35b489baf3615236b74f62efe2f33be 100644
|
||||||
|
--- a/net/minecraft/world/level/chunk/LevelChunk.java
|
||||||
|
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
|
||||||
|
@@ -358,6 +358,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public BlockState setBlockState(BlockPos pos, BlockState state, int flags) {
|
||||||
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, pos, "Updating block asynchronously"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
||||||
|
int y = pos.getY();
|
||||||
|
LevelChunkSection section = this.getSection(this.getSectionIndex(y));
|
||||||
|
boolean hasOnlyAir = section.hasOnlyAir();
|
||||||
|
diff --git a/net/minecraft/world/level/entity/EntityTickList.java b/net/minecraft/world/level/entity/EntityTickList.java
|
||||||
|
index 423779a2b690f387a4f0bd07b97b50e0baefda76..24f9632e73d73c2ad68ebf30eb0e4cca7befae2d 100644
|
||||||
|
--- a/net/minecraft/world/level/entity/EntityTickList.java
|
||||||
|
+++ b/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,6 +39,7 @@ public class EntityTickList {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void forEach(Consumer<Entity> entity) {
|
||||||
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverLevel, "Asynchronous entity ticklist iteration"); // SparklyPaper - parallel world ticking (additional concurrency issues logs)
|
||||||
|
// Paper start - rewrite chunk system
|
||||||
|
// 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)
|
||||||
@@ -0,0 +1,626 @@
|
|||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: MrPowerGamerBR <git@mrpowergamerbr.com>
|
||||||
|
Date: Sun, 25 May 2025 21:40:06 -0300
|
||||||
|
Subject: [PATCH] Parallel World Ticking
|
||||||
|
|
||||||
|
|
||||||
|
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java
|
||||||
|
index 69cdd304d255d52c9b7dc9b6a33ffdb630b79abe..8fc5691d608359ce848b8caecdd55ddd4372abfa 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
|
||||||
|
|
||||||
|
private static String getThreadContext() {
|
||||||
|
return "thread=" + Thread.currentThread().getName();
|
||||||
|
@@ -26,6 +27,7 @@ public class TickThread extends Thread {
|
||||||
|
public static void ensureTickThread(final String reason) {
|
||||||
|
if (!isTickThread()) {
|
||||||
|
LOGGER.error("Thread failed main thread check: " + reason + ", context=" + getThreadContext(), new Throwable());
|
||||||
|
+ if (HARD_THROW) // SparklyPaper - parallel world ticking
|
||||||
|
throw new IllegalStateException(reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -33,8 +35,9 @@ public class TickThread extends Thread {
|
||||||
|
public static void ensureTickThread(final Level world, final BlockPos pos, final String reason) {
|
||||||
|
if (!isTickThreadFor(world, pos)) {
|
||||||
|
final String ex = "Thread failed main thread check: " +
|
||||||
|
- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos;
|
||||||
|
+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + " - " + getTickThreadInformation(world.getServer());
|
||||||
|
LOGGER.error(ex, new Throwable());
|
||||||
|
+ if (HARD_THROW) // SparklyPaper - parallel world ticking
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -42,8 +45,9 @@ public class TickThread extends Thread {
|
||||||
|
public static void ensureTickThread(final Level world, final BlockPos pos, final int blockRadius, final String reason) {
|
||||||
|
if (!isTickThreadFor(world, pos, blockRadius)) {
|
||||||
|
final String ex = "Thread failed main thread check: " +
|
||||||
|
- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + ", block_radius=" + blockRadius;
|
||||||
|
+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + pos + ", block_radius=" + blockRadius + " - " + getTickThreadInformation(world.getServer());
|
||||||
|
LOGGER.error(ex, new Throwable());
|
||||||
|
+ if (HARD_THROW) // SparklyPaper - parallel world ticking
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -51,8 +55,9 @@ public class TickThread extends Thread {
|
||||||
|
public static void ensureTickThread(final Level world, final ChunkPos pos, final String reason) {
|
||||||
|
if (!isTickThreadFor(world, pos)) {
|
||||||
|
final String ex = "Thread failed main thread check: " +
|
||||||
|
- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + pos;
|
||||||
|
+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + pos + " - " + getTickThreadInformation(world.getServer());
|
||||||
|
LOGGER.error(ex, new Throwable());
|
||||||
|
+ if (HARD_THROW) // SparklyPaper - parallel world ticking
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -60,8 +65,9 @@ public class TickThread extends Thread {
|
||||||
|
public static void ensureTickThread(final Level world, final int chunkX, final int chunkZ, final String reason) {
|
||||||
|
if (!isTickThreadFor(world, chunkX, chunkZ)) {
|
||||||
|
final String ex = "Thread failed main thread check: " +
|
||||||
|
- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + new ChunkPos(chunkX, chunkZ);
|
||||||
|
+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", chunk_pos=" + new ChunkPos(chunkX, chunkZ) + " - " + getTickThreadInformation(world.getServer());
|
||||||
|
LOGGER.error(ex, new Throwable());
|
||||||
|
+ if (HARD_THROW) // SparklyPaper - parallel world ticking
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -69,8 +75,9 @@ public class TickThread extends Thread {
|
||||||
|
public static void ensureTickThread(final Entity entity, final String reason) {
|
||||||
|
if (!isTickThreadFor(entity)) {
|
||||||
|
final String ex = "Thread failed main thread check: " +
|
||||||
|
- reason + ", context=" + getThreadContext() + ", entity=" + EntityUtil.dumpEntity(entity);
|
||||||
|
+ reason + ", context=" + getThreadContext() + ", entity=" + EntityUtil.dumpEntity(entity) + " - " + getTickThreadInformation(entity.getServer());
|
||||||
|
LOGGER.error(ex, new Throwable());
|
||||||
|
+ if (HARD_THROW) // SparklyPaper - parallel world ticking
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -78,8 +85,9 @@ public class TickThread extends Thread {
|
||||||
|
public static void ensureTickThread(final Level world, final AABB aabb, final String reason) {
|
||||||
|
if (!isTickThreadFor(world, aabb)) {
|
||||||
|
final String ex = "Thread failed main thread check: " +
|
||||||
|
- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb;
|
||||||
|
+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", aabb=" + aabb + " - " + getTickThreadInformation(world.getServer());
|
||||||
|
LOGGER.error(ex, new Throwable());
|
||||||
|
+ if (HARD_THROW) // SparklyPaper - parallel world ticking
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -87,12 +95,75 @@ public class TickThread extends Thread {
|
||||||
|
public static void ensureTickThread(final Level world, final double blockX, final double blockZ, final String reason) {
|
||||||
|
if (!isTickThreadFor(world, blockX, blockZ)) {
|
||||||
|
final String ex = "Thread failed main thread check: " +
|
||||||
|
- reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + new Vec3(blockX, 0.0, blockZ);
|
||||||
|
+ reason + ", context=" + getThreadContext() + ", world=" + WorldUtil.getWorldName(world) + ", block_pos=" + new Vec3(blockX, 0.0, blockZ) + " - " + getTickThreadInformation(world.getServer());
|
||||||
|
LOGGER.error(ex, new Throwable());
|
||||||
|
+ if (HARD_THROW) // SparklyPaper - parallel world ticking
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // 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();
|
||||||
|
@@ -127,7 +198,11 @@ 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 BlockPos pos, final int blockRadius) {
|
||||||
|
@@ -135,38 +210,103 @@ public class TickThread extends Thread {
|
||||||
|
}
|
||||||
|
|
||||||
|
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/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
||||||
|
index aae378697b2f2e388d2a5dfaca24c9197b8abf3e..4b2367be92e28331d4e15bab68333d3afcd61950 100644
|
||||||
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
||||||
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
||||||
|
@@ -456,7 +456,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;
|
||||||
|
}
|
||||||
|
@@ -473,6 +473,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;
|
||||||
|
|
||||||
|
@@ -523,6 +524,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean loadChunk(int x, int z, boolean generate) {
|
||||||
|
+ 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)
|
||||||
|
org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot
|
||||||
|
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
|
||||||
|
@@ -751,6 +753,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);
|
||||||
|
@@ -866,6 +869,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
|
||||||
|
@@ -957,6 +961,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);
|
||||||
|
@@ -987,6 +992,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);
|
||||||
|
|
||||||
|
@@ -2295,6 +2301,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 a4d5c65edc1db59f3486ce5d3757cc306211a54b..3609e71f382f80017acb91c5e286008d1684d224 100644
|
||||||
|
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
|
||||||
|
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
|
||||||
|
@@ -155,6 +155,11 @@ public class CraftBlock implements Block {
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setData(final byte data, int flags) {
|
||||||
|
+ // 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), flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -196,6 +201,11 @@ public class CraftBlock implements Block {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean setBlockState(LevelAccessor world, BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState, boolean applyPhysics) {
|
||||||
|
+ // SparklyPaper start - parallel world ticking
|
||||||
|
+ if (world instanceof ServerLevel serverWorld) {
|
||||||
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, pos, "Cannot modify world asynchronously");
|
||||||
|
+ }
|
||||||
|
+ // SparklyPaper end - parallel world ticking
|
||||||
|
// SPIGOT-611: need to do this to prevent glitchiness. Easier to handle this here (like /setblock) than to fix weirdness in block entity cleanup
|
||||||
|
if (oldState.hasBlockEntity() && newState.getBlock() != oldState.getBlock()) { // SPIGOT-3725 remove old block entity if block changes
|
||||||
|
// SPIGOT-4612: faster - just clear tile
|
||||||
|
@@ -344,18 +354,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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -376,6 +401,11 @@ public class CraftBlock implements Block {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBlockIndirectlyPowered() {
|
||||||
|
+ // 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.world.getMinecraftWorld().hasNeighborSignal(this.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -415,6 +445,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();
|
||||||
|
@@ -499,6 +534,11 @@ public class CraftBlock implements Block {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean breakNaturally(ItemStack item, boolean triggerEffect, boolean dropExperience) {
|
||||||
|
+ // 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
|
||||||
|
// Paper end
|
||||||
|
// Order matters here, need to drop before setting to air so skulls can get their data
|
||||||
|
net.minecraft.world.level.block.state.BlockState state = this.getNMS();
|
||||||
|
@@ -542,6 +582,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();
|
||||||
|
@@ -553,8 +598,8 @@ public class CraftBlock implements Block {
|
||||||
|
world.captureTreeGeneration = false;
|
||||||
|
|
||||||
|
if (!world.capturedBlockStates.isEmpty()) {
|
||||||
|
- 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> states = new ArrayList<>(world.capturedBlockStates.values());
|
||||||
|
world.capturedBlockStates.clear();
|
||||||
|
StructureGrowEvent structureEvent = null;
|
||||||
|
@@ -593,6 +638,11 @@ public class CraftBlock implements Block {
|
||||||
|
@Override
|
||||||
|
public Collection<ItemStack> getDrops(ItemStack item, Entity entity) {
|
||||||
|
net.minecraft.world.level.block.state.BlockState state = this.getNMS();
|
||||||
|
+ // 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
|
||||||
|
net.minecraft.world.item.ItemStack nms = CraftItemStack.asNMSCopy(item);
|
||||||
|
|
||||||
|
// Modelled off Player#hasCorrectToolForDrops
|
||||||
|
@@ -644,6 +694,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 +740,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();
|
||||||
|
@@ -724,6 +784,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 3422970353dcd886934b9ee906467769d39abbde..79f53cfa396ae67b1e5432ca353cebafae9499fa 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 blockEntity;
|
||||||
|
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 blockEntity) {
|
||||||
|
super(world, blockEntity.getBlockPos(), blockEntity.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.blockEntity;
|
||||||
|
} else {
|
||||||
|
this.snapshot = this.createSnapshot(blockEntity);
|
||||||
|
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
|
||||||
|
index 196835bdf95ba0e149b2977e9ef41698971f501f..641adf9666fef4d15bc9b585aabfe687a83fbe86 100644
|
||||||
|
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
|
||||||
|
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
|
||||||
|
@@ -218,6 +218,12 @@ public class CraftBlockState implements BlockState {
|
||||||
|
LevelAccessor access = this.getWorldHandle();
|
||||||
|
CraftBlock block = this.getBlock();
|
||||||
|
|
||||||
|
+ // SparklyPaper start - parallel world ticking
|
||||||
|
+ if (access instanceof net.minecraft.server.level.ServerLevel serverWorld) {
|
||||||
|
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverWorld, position, "Cannot modify world asynchronously");
|
||||||
|
+ }
|
||||||
|
+ // SparklyPaper end - parallel world ticking
|
||||||
|
+
|
||||||
|
if (block.getType() != this.getType()) {
|
||||||
|
if (!force) {
|
||||||
|
return false;
|
||||||
|
@@ -365,6 +371,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 2338e7c115037430cefae26a571ded71f77983c4..15ba9c5588b6ef8da7e495dccbdcd53711ee4af1 100644
|
||||||
|
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java
|
||||||
|
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java
|
||||||
|
@@ -195,14 +195,14 @@ public final class CraftBlockStates {
|
||||||
|
BlockPos pos = craftBlock.getPosition();
|
||||||
|
net.minecraft.world.level.block.state.BlockState state = craftBlock.getNMS();
|
||||||
|
BlockEntity blockEntity = craftBlock.getHandle().getBlockEntity(pos);
|
||||||
|
- boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT;
|
||||||
|
- CraftBlockEntityState.DISABLE_SNAPSHOT = !useSnapshot;
|
||||||
|
+ boolean prev = CraftBlockEntityState.DISABLE_SNAPSHOT.get(); // SparklyPaper - parallel world ticking
|
||||||
|
+ CraftBlockEntityState.DISABLE_SNAPSHOT.set(!useSnapshot); // SparklyPaper - parallel world ticking
|
||||||
|
try {
|
||||||
|
CraftBlockState blockState = CraftBlockStates.getBlockState(world, pos, state, blockEntity);
|
||||||
|
blockState.setWorldHandle(craftBlock.getHandle()); // Inject the block's generator access
|
||||||
|
return blockState;
|
||||||
|
} finally {
|
||||||
|
- CraftBlockEntityState.DISABLE_SNAPSHOT = prev;
|
||||||
|
+ CraftBlockEntityState.DISABLE_SNAPSHOT.set(prev); // SparklyPaper - parallel world ticking
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
|
||||||
|
index b60d1816fe97c01d13ba46311c57a0fc29e86e19..077f3d3173bfab9196b504a0386479e3af41f59a 100644
|
||||||
|
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
|
||||||
|
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
|
||||||
|
@@ -808,7 +808,7 @@ public class CraftEventFactory {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
- public static BlockPos sourceBlockOverride = null; // SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPos up to five methods deep.
|
||||||
|
+ public static final ThreadLocal<BlockPos> sourceBlockOverrideRT = new ThreadLocal<>(); // 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 state, int flags) {
|
||||||
|
return handleBlockSpreadEvent(world, source, target, state, flags, false);
|
||||||
|
@@ -824,7 +824,7 @@ public class CraftEventFactory {
|
||||||
|
CraftBlockState snapshot = CraftBlockStates.getBlockState(world, target);
|
||||||
|
snapshot.setData(state);
|
||||||
|
|
||||||
|
- BlockSpreadEvent event = new BlockSpreadEvent(snapshot.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverride != null ? CraftEventFactory.sourceBlockOverride : source), snapshot);
|
||||||
|
+ BlockSpreadEvent event = new BlockSpreadEvent(snapshot.getBlock(), CraftBlock.at(world, CraftEventFactory.sourceBlockOverrideRT.get() != null ? CraftEventFactory.sourceBlockOverrideRT.get() : source), snapshot);
|
||||||
|
if (event.callEvent()) {
|
||||||
|
boolean result = snapshot.place(flags);
|
||||||
|
return !checkSetResult || result;
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user