9
0
mirror of https://github.com/BX-Team/DivineMC.git synced 2025-12-19 14:59:25 +00:00
Files
DivineMC/divinemc-server/minecraft-patches/features/0045-Parallel-world-ticking.patch
Artem Ostrasev 109c57a637 1.21.6 (#25)
* start 1.21.6 update

* change workflow

* apply some patches

* set experimental to true

* some compile fixes

* Updated Upstream (Purpur)

Upstream has released updates that appear to apply and compile correctly

Purpur Changes:
PurpurMC/Purpur@5d3463aa Updated Upstream (Paper)

* Updated Upstream (Purpur)

Upstream has released updates that appear to apply and compile correctly

Purpur Changes:
PurpurMC/Purpur@5d3463aa Updated Upstream (Paper)

* remove data converter for 1.21.6; move clumps to unapplied

* Updated Upstream (Purpur)

Upstream has released updates that appear to apply and compile correctly

* Updated Upstream (Purpur)

Upstream has released updates that appear to apply and compile correctly

* Update upstream

* Update upstream

* update to 1.21.6-pre4

* update patches

* fix compile

* set readme version to 1.21.6

* Updated Upstream (Purpur)

Upstream has released updates that appear to apply and compile correctly

Purpur Changes:
PurpurMC/Purpur@439f15db Updated Upstream (Paper)
PurpurMC/Purpur@46a28b93 [ci/skip] update version in README
2025-06-18 17:36:28 +03:00

1083 lines
71 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com>
Date: Wed, 29 Jan 2025 00:59:03 +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 60705955e231d47c60f5a0a5e869988aed8774fc..c0322d4f9eadb26b8d5c6a3b426218ebb6302608 100644
--- a/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
+++ b/ca/spottedleaf/moonrise/patches/chunk_system/scheduling/ChunkHolderManager.java
@@ -1129,7 +1129,7 @@ public final class ChunkHolderManager {
if (changedFullStatus.isEmpty()) {
return;
}
- if (!TickThread.isTickThread()) {
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && !TickThread.isTickThreadFor(world)) { // DivineMC - Parallel world ticking
this.taskScheduler.scheduleChunkTask(() -> {
final java.util.Deque<NewChunkHolder> pendingFullLoadUpdate = ChunkHolderManager.this.getData().pendingFullLoadUpdate; // DivineMC - Chunk System optimization
for (int i = 0, len = changedFullStatus.size(); i < len; ++i) {
@@ -1155,7 +1155,13 @@ 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");
+ // DivineMC start - Parallel world ticking
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) {
+ TickThread.ensureTickThread(world, "Cannot unload chunks off-main");
+ } else {
+ TickThread.ensureTickThread("Cannot unload chunks off-main");
+ }
+ // DivineMC end - Parallel world ticking
if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) {
throw new IllegalStateException("Cannot unload chunks recursively");
@@ -1458,7 +1464,7 @@ public final class ChunkHolderManager {
List<NewChunkHolder> changedFullStatus = null;
- final boolean isTickThread = TickThread.isTickThread();
+ final boolean isTickThread = org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && TickThread.isTickThreadFor(world) || TickThread.isTickThread(); // DivineMC - Parallel world ticking
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 ac27ff24f018d8798921c5152e679ceed1e88d8d..ec7d1353b19e55b00c558df8981323efb9b88bdf 100644
--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java
@@ -401,8 +401,10 @@ 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;
+ // DivineMC start - Parallel world ticking
+ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.getTreeTypeTL();
+ net.minecraft.world.level.block.SaplingBlock.setTreeTypeTL(null);
+ // DivineMC end - 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 9e83d0ec1a0976f365fa6cb3f1b7db167641b336..6f08d761ee52d65a57fd01cf7bffa461dc1b898b 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -291,6 +291,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public final org.bxteam.divinemc.util.tps.TPSCalculator tpsCalculator = new org.bxteam.divinemc.util.tps.TPSCalculator(); // DivineMC - Lag compensation
public final Set<net.minecraft.world.entity.Entity> entitiesWithScheduledTasks = java.util.concurrent.ConcurrentHashMap.newKeySet(); // DivineMC - Skip EntityScheduler's executeTick checks if there isn't any tasks to be run
public org.bxteam.divinemc.util.AsyncProcessor mobSpawnExecutor = new org.bxteam.divinemc.util.AsyncProcessor("mob_spawning"); // DivineMC - Async mob spawning
+ public java.util.concurrent.Semaphore serverLevelTickingSemaphore = null; // DivineMC - Parallel world ticking
public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
@@ -323,24 +324,36 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
private long lastMidTickExecute;
private long lastMidTickExecuteFailure;
+ // DivineMC start - Parallel world ticking
+ private boolean tickLevelMidTickTasks(ServerLevel world) {
+ long currTime = System.nanoTime();
+ if (currTime - ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)world).moonrise$getLastMidTickFailure() <= TASK_EXECUTION_FAILURE_BACKOFF) {
+ return false;
+ }
+ if (!world.getChunkSource().pollTask()) {
+ // we need to back off if this fails
+ ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)world).moonrise$setLastMidTickFailure(currTime);
+ return false;
+ }
+ return true;
+ }
+ // DivineMC end - Parallel world ticking
+
private boolean tickMidTickTasks() {
// give all worlds a fair chance at by targeting them all.
// if we execute too many tasks, that's fine - we have logic to correctly handle overuse of allocated time.
- boolean executed = false;
- for (final ServerLevel world : this.getAllLevels()) {
- long currTime = System.nanoTime();
- if (currTime - ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)world).moonrise$getLastMidTickFailure() <= TASK_EXECUTION_FAILURE_BACKOFF) {
- continue;
- }
- if (!world.getChunkSource().pollTask()) {
- // we need to back off if this fails
- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)world).moonrise$setLastMidTickFailure(currTime);
- } else {
- executed = true;
+ // DivineMC start - Parallel world ticking
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && Thread.currentThread() instanceof ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread levelThread) {
+ return this.tickLevelMidTickTasks(levelThread.currentlyTickingServerLevel);
+ } else {
+ boolean executed = false;
+ for (final ServerLevel world : this.getAllLevels()) {
+ executed = executed || this.tickLevelMidTickTasks(world);
}
- }
- return executed;
+ return executed;
+ }
+ // DivineMC end - Parallel world ticking
}
@Override
@@ -1658,6 +1671,18 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
}
+ // DivineMC start - Parallel world ticking
+ private void tickLevel(ServerLevel serverLevel, BooleanSupplier hasTimeLeft) {
+ try {
+ serverLevel.tick(hasTimeLeft);
+ } catch (Throwable levelTickingException) {
+ CrashReport crashReport = CrashReport.forThrowable(levelTickingException, "Exception ticking world");
+ serverLevel.fillReportDetails(crashReport);
+ throw new ReportedException(crashReport);
+ }
+ }
+ // DivineMC end - Parallel world ticking
+
protected void tickChildren(BooleanSupplier hasTimeLeft) {
this.getPlayerList().getPlayers().forEach(serverPlayer1 -> serverPlayer1.connection.suspendFlushing());
this.server.getScheduler().mainThreadHeartbeat(); // CraftBukkit
@@ -1707,28 +1732,43 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
this.isIteratingOverLevels = true; // Paper - Throw exception on world create while being ticked
- 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
- serverLevel.updateLagCompensationTick(); // Paper - lag compensation
- net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = serverLevel.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers
- serverLevel.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur - Ridables
- /* Drop global time updates
- if (this.tickCount % 20 == 0) {
- this.synchronizeTime(serverLevel);
- }
- // CraftBukkit end */
+ // DivineMC 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
+ serverLevel.updateLagCompensationTick(); // Paper - lag compensation
+ net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = serverLevel.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers
+ serverLevel.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur - Ridables
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) {
+ serverLevelTickingSemaphore.acquire();
+ tasks.add(
+ serverLevel.tickExecutor.submit(() -> {
+ ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread currentThread = (ca.spottedleaf.moonrise.common.util.TickThread.ServerLevelTickThread) Thread.currentThread();
+ currentThread.currentlyTickingServerLevel = serverLevel;
- try {
- serverLevel.tick(hasTimeLeft);
- } catch (Throwable var7) {
- CrashReport crashReport = CrashReport.forThrowable(var7, "Exception ticking world");
- serverLevel.fillReportDetails(crashReport);
- throw new ReportedException(crashReport);
+ try {
+ tickLevel(serverLevel, hasTimeLeft);
+ } finally {
+ serverLevelTickingSemaphore.release();
+ }
+ }, serverLevel)
+ );
+ } else {
+ tickLevel(serverLevel, hasTimeLeft);
+ }
+
+ serverLevel.explosionDensityCache.clear(); // Paper - Optimize explosions
}
- 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);
}
+ // DivineMC end - Parallel world ticking
this.isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
this.tickConnection();
@@ -1806,6 +1846,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());
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) level.tickExecutor.shutdown(); // DivineMC - Parallel world ticking
this.levels = Collections.unmodifiableMap(newLevels);
}
// CraftBukkit end
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index 19a3ba355694e7c26c233b216807cb23c82548be..245ffc68aac80d99849290bfefe5cb613eef5d81 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -224,6 +224,13 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
}
// DivineMC end - Pufferfish SIMD
+ // DivineMC start - Parallel world ticking
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) {
+ serverLevelTickingSemaphore = new java.util.concurrent.Semaphore(org.bxteam.divinemc.config.DivineConfig.AsyncCategory.parallelThreadCount);
+ DedicatedServer.LOGGER.info("Using {} permits for Parallel world ticking", serverLevelTickingSemaphore.availablePermits());
+ }
+ // DivineMC end - Parallel world ticking
+
this.setPvpAllowed(properties.pvp);
this.setFlightAllowed(properties.allowFlight);
this.setMotd(properties.motd);
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index e05a0eed63098c535360b66276f154e4dc119d6a..0102d87411dcf0bba7f6873dc54385c957a7e2d7 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -181,7 +181,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
public final net.minecraft.world.level.storage.PrimaryLevelData serverLevelData; // CraftBukkit - type
public final ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager.LevelHolderData chunkHolderData; // DivineMC - Chunk System optimization
private int lastSpawnChunkRadius;
- final EntityTickList entityTickList = new EntityTickList();
+ final EntityTickList entityTickList = new EntityTickList(this); // DivineMC - Parallel world ticking
private final ServerWaypointManager waypointManager;
// Paper - rewrite chunk system
private final GameEventDispatcher gameEventDispatcher;
@@ -208,6 +208,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
private double preciseTime; // Purpur - Configurable daylight cycle
private boolean forceTime; // Purpur - Configurable daylight cycle
private final RandomSequences randomSequences;
+ public java.util.concurrent.ExecutorService tickExecutor; // DivineMC - Parallel world ticking
// CraftBukkit start
public final LevelStorageSource.LevelStorageAccess levelStorageAccess;
@@ -698,6 +699,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 org.bxteam.divinemc.server.ServerLevelTickExecutorThreadFactory(getWorld().getName())); // DivineMC - Parallel world ticking
this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle
this.chunkSystemPriorities = new org.bxteam.divinemc.server.chunk.PriorityHandler(this); // DivineMC - Chunk System optimizations
}
@@ -1300,12 +1302,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
if (fluidState.is(fluid)) {
fluidState.tick(this, pos, blockState);
}
- // Paper start - rewrite chunk system
- if ((++this.tickedBlocksOrFluids & 7L) != 0L) {
- ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks();
+ // DivineMC start - Parallel world ticking
+ ++this.tickedBlocksOrFluids;
+ if (!org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && (this.tickedBlocksOrFluids & 7L) != 0L) {
+ this.server.moonrise$executeMidTickTasks();
}
- // Paper end - rewrite chunk system
-
+ // DivineMC end - Parallel world ticking
}
private void tickBlock(BlockPos pos, Block block) {
@@ -1313,12 +1315,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
if (blockState.is(block)) {
blockState.tick(this, pos, this.random);
}
- // Paper start - rewrite chunk system
- if ((++this.tickedBlocksOrFluids & 7L) != 0L) {
- ((ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks();
+ // DivineMC start - Parallel world ticking
+ ++this.tickedBlocksOrFluids;
+ if (!org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && (this.tickedBlocksOrFluids & 7L) != 0L) {
+ this.server.moonrise$executeMidTickTasks();
}
- // Paper end - rewrite chunk system
-
+ // DivineMC end - Parallel world ticking
}
// Paper start - log detailed entity tick information
@@ -1568,6 +1570,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
}
private void addPlayer(ServerPlayer player) {
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add player off-main"); // DivineMC - Parallel world ticking
Entity entity = this.getEntity(player.getUUID());
if (entity != null) {
LOGGER.warn("Force-added player with duplicate UUID {}", player.getUUID());
@@ -1580,7 +1583,13 @@ 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
+ // DivineMC start - Parallel world ticking
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) {
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this, "Cannot add entity off-main");
+ } else {
+ org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot
+ }
+ // DivineMC end - Parallel world ticking
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 bedc28c355b2c1e5a2ca4a1559c29101525c51e2..3b87d3f889db67e4913691b2cd50dddd2a2faf7d 100644
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -463,6 +463,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
return this.viewDistanceHolder;
}
// Paper end - rewrite chunk system
+ public boolean hasTickedAtLeastOnceInNewWorld = false; // DivineMC - Parallel world ticking
public ServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) {
super(level, gameProfile);
@@ -750,6 +751,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
@Override
public void tick() {
+ hasTickedAtLeastOnceInNewWorld = true; // DivineMC - Parallel world ticking
// CraftBukkit start
if (this.joining) {
this.joining = false;
@@ -1427,6 +1429,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
return this;
} else {
// CraftBukkit start
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot change dimension of a player off-main, from world " + level().getWorld().getName() + " to world " + level.getWorld().getName()); // DivineMC - Parallel world ticking (additional concurrency issues logs)
/*
this.isChangingDimension = true;
LevelData levelData = level.getLevelData();
@@ -1772,6 +1775,12 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
return OptionalInt.empty();
} else {
// CraftBukkit start
+ // DivineMC start - Parallel world ticking
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && !hasTickedAtLeastOnceInNewWorld) {
+ MinecraftServer.LOGGER.warn("Ignoring request to open container {} because we haven't ticked in the current world yet!", abstractContainerMenu, new Throwable());
+ return OptionalInt.empty();
+ }
+ // DivineMC end - Parallel world ticking
this.containerMenu = abstractContainerMenu; // Moved up
if (!this.isImmobile())
this.connection
@@ -1836,6 +1845,11 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc
}
@Override
public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
+ // DivineMC start - Parallel world ticking (debugging)
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.logContainerCreationStacktraces) {
+ MinecraftServer.LOGGER.warn("Closing {} inventory that was created at", this.getBukkitEntity().getName(), this.containerMenu.containerCreationStacktrace);
+ }
+ // DivineMC end - Parallel world ticking (debugging)
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 8bf5f7a98e4e8542d56c49a7e2c4926ec86f712d..e1ca822d41311e3be44c52badb907619ca681cf9 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -149,6 +149,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) {
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot place new player off-main"); // DivineMC - Parallel world ticking
player.isRealPlayer = true; // Paper
player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed
GameProfile gameProfile = player.getGameProfile();
@@ -711,6 +712,14 @@ 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) {
+ // DivineMC start - Parallel world ticking
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) {
+ if (location != null)
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, from world " + player.level().getWorld().getName() + " to world " + location.getWorld().getName());
+ else
+ ca.spottedleaf.moonrise.common.util.TickThread.ensureOnlyTickThread("Cannot respawn player off-main, respawning in world " + player.level().getWorld().getName());
+ }
+ // DivineMC end - Parallel world ticking
this.players.remove(player);
this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot
player.level().removePlayerImmediately(player, reason);
@@ -720,6 +729,7 @@ public abstract class PlayerList {
ServerPlayer serverPlayer = player;
Level fromWorld = player.level();
player.wonGame = false;
+ serverPlayer.hasTickedAtLeastOnceInNewWorld = false; // DivineMC - Parallel world ticking
// 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 e96eeae793d127b76a2fe6853cba49ce192b91f9..3aa2069ff070d9edb596e3c08cd34c617edd955c 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -3432,14 +3432,34 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
if (this.portalProcess != null) {
if (this.portalProcess.processPortalTeleportation(serverLevel, this, this.canUsePortal(false))) {
this.setPortalCooldown();
- TeleportTransition portalDestination = this.portalProcess.getPortalDestination(serverLevel, this);
- if (portalDestination != null) {
- ServerLevel level = portalDestination.newLevel();
- if (this instanceof ServerPlayer // CraftBukkit - always call event for players
- || (level != null && (level.dimension() == serverLevel.dimension() || this.canTeleport(serverLevel, level)))) { // CraftBukkit
- this.teleport(portalDestination);
+ // DivineMC start - Parallel world ticking
+ java.util.function.Consumer<Entity> portalEntityTask = entity -> {
+ assert entity.portalProcess != null;
+
+ if (entity.portalProcess.isParallelCancelledByPlugin()) {
+ entity.portalProcess = null;
+ return;
+ }
+
+ TeleportTransition portalDestination = entity.portalProcess.getPortalDestination(serverLevel, entity);
+ if (portalDestination != null) {
+ ServerLevel level = portalDestination.newLevel();
+ if (entity instanceof ServerPlayer // CraftBukkit - always call event for players
+ || (level != null && (level.dimension() == serverLevel.dimension() || entity.canTeleport(serverLevel, level)))) { // CraftBukkit
+ entity.teleport(portalDestination);
+ }
}
+ if (this.portalProcess != null)
+ entity.portalProcess.confirmParallelAsHandled();
+ };
+
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) {
+ this.portalProcess.setParallelAsScheduled();
+ this.getBukkitEntity().taskScheduler.schedule(portalEntityTask, entity -> {}, 0);
+ } else {
+ portalEntityTask.accept(this);
}
+ // DivineMC end - Parallel world ticking
} else if (this.portalProcess.hasExpired()) {
this.portalProcess = null;
}
@@ -4016,6 +4036,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
private Entity teleportCrossDimension(ServerLevel oldLevel, ServerLevel newLevel, TeleportTransition teleportTransition) {
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(newLevel, "Cannot teleport entity to another world off-main, from world " + oldLevel.getWorld().getName() + " to world " + newLevel.getWorld().getName()); // DivineMC - Parallel world ticking
List<Entity> passengers = this.getPassengers();
List<Entity> list = new ArrayList<>(passengers.size());
this.ejectPassengers();
diff --git a/net/minecraft/world/entity/PortalProcessor.java b/net/minecraft/world/entity/PortalProcessor.java
index 91f6d43b3785ddad7db8eb529ba3293c45f3588d..fc3ab0881bf9b275beb9b32ca5a7475d50789401 100644
--- a/net/minecraft/world/entity/PortalProcessor.java
+++ b/net/minecraft/world/entity/PortalProcessor.java
@@ -11,6 +11,7 @@ public class PortalProcessor {
private BlockPos entryPosition;
private int portalTime;
private boolean insidePortalThisTick;
+ private org.bxteam.divinemc.util.PWTTeleportState teleportState = org.bxteam.divinemc.util.PWTTeleportState.INACTIVE; // DivineMC - Parallel world ticking
public PortalProcessor(Portal portal, BlockPos entryPosition) {
this.portal = portal;
@@ -19,6 +20,7 @@ public class PortalProcessor {
}
public boolean processPortalTeleportation(ServerLevel level, Entity entity, boolean canChangeDimensions) {
+ if (this.isParallelTeleportScheduled()) return false; // DivineMC - Parallel world ticking
if (!this.insidePortalThisTick) {
this.decayTick();
return false;
@@ -52,7 +54,7 @@ public class PortalProcessor {
}
public boolean hasExpired() {
- return this.portalTime <= 0;
+ return !this.isParallelTeleportScheduled() && this.portalTime <= 0; // DivineMC - Parallel world ticking
}
public BlockPos getEntryPosition() {
@@ -78,4 +80,30 @@ public class PortalProcessor {
public boolean isSamePortal(Portal portal) {
return this.portal == portal;
}
+
+ // DivineMC start - Parallel world ticking
+ public boolean isParallelTeleportPending() {
+ return this.teleportState == org.bxteam.divinemc.util.PWTTeleportState.PENDING;
+ }
+
+ public boolean isParallelTeleportScheduled() {
+ return this.teleportState != org.bxteam.divinemc.util.PWTTeleportState.INACTIVE;
+ }
+
+ public boolean isParallelCancelledByPlugin() {
+ return this.teleportState == org.bxteam.divinemc.util.PWTTeleportState.CANCELLED;
+ }
+
+ public void setParallelAsScheduled() {
+ this.teleportState = org.bxteam.divinemc.util.PWTTeleportState.PENDING;
+ }
+
+ public void confirmParallelAsHandled() {
+ this.teleportState = org.bxteam.divinemc.util.PWTTeleportState.INACTIVE;
+ }
+
+ public void setParallelAsCancelled() {
+ this.teleportState = org.bxteam.divinemc.util.PWTTeleportState.CANCELLED;
+ }
+ // DivineMC end - Parallel world ticking
}
diff --git a/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java b/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java
index 3614551856c594f3c0cfee984fcf03fad672b007..ad52152d090c32b81044b8fd9496eae0b4e755b0 100644
--- a/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java
+++ b/net/minecraft/world/entity/ai/behavior/GoToPotentialJobSite.java
@@ -46,12 +46,22 @@ public class GoToPotentialJobSite extends Behavior<Villager> {
BlockPos blockPos = globalPos.pos();
ServerLevel level1 = level.getServer().getLevel(globalPos.dimension());
if (level1 != null) {
- PoiManager poiManager = level1.getPoiManager();
- if (poiManager.exists(blockPos, holder -> true)) {
- poiManager.release(blockPos);
- }
+ // DivineMC start - Parallel world ticking
+ Runnable releasePoiTask = () -> {
+ PoiManager poiManager = level1.getPoiManager();
+ if (poiManager.exists(blockPos, holder -> true)) {
+ poiManager.release(blockPos);
+ }
+
+ DebugPackets.sendPoiTicketCountPacket(level, blockPos);
+ };
- DebugPackets.sendPoiTicketCountPacket(level, blockPos);
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) {
+ level.moonrise$getChunkTaskScheduler().scheduleChunkTask(0, 0, releasePoiTask, ca.spottedleaf.concurrentutil.util.Priority.BLOCKING);
+ } else {
+ releasePoiTask.run();
+ }
+ // DivineMC end - Parallel world ticking
}
});
entity.getBrain().eraseMemory(MemoryModuleType.POTENTIAL_JOB_SITE);
diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java
index 3e23bc54e0315b6324843851e072005ca4da3f8e..50130de436bb12334f5b7f63bf6bf30578b569b2 100644
--- a/net/minecraft/world/entity/npc/Villager.java
+++ b/net/minecraft/world/entity/npc/Villager.java
@@ -796,13 +796,24 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
this.brain.getMemory(moduleType).ifPresent(pos -> {
ServerLevel level = server.getLevel(pos.dimension());
if (level != null) {
- PoiManager poiManager = level.getPoiManager();
- Optional<Holder<PoiType>> type = poiManager.getType(pos.pos());
- BiPredicate<Villager, Holder<PoiType>> biPredicate = POI_MEMORIES.get(moduleType);
- if (type.isPresent() && biPredicate.test(this, type.get())) {
- poiManager.release(pos.pos());
- DebugPackets.sendPoiTicketCountPacket(level, pos.pos());
+ // DivineMC start - Parallel world ticking
+ Runnable releasePoiTask = () -> {
+ PoiManager poiManager = level.getPoiManager();
+ Optional<Holder<PoiType>> type = poiManager.getType(pos.pos());
+ BiPredicate<Villager, Holder<PoiType>> biPredicate = POI_MEMORIES.get(moduleType);
+ if (type.isPresent() && biPredicate.test(this, type.get())) {
+ poiManager.release(pos.pos());
+ DebugPackets.sendPoiTicketCountPacket(level, pos.pos());
+ }
+ };
+
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) {
+ level.moonrise$getChunkTaskScheduler().scheduleChunkTask(0, 0, releasePoiTask, ca.spottedleaf.concurrentutil.util.Priority.BLOCKING);
+ }
+ else {
+ releasePoiTask.run();
}
+ // DivineMC end - Parallel world ticking
}
});
}
diff --git a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
index 2258736e6f9f52efe5bd353b8949a7a0b9a4fdb8..440fbe301782e81cec679a27a876dd3c0bf8615e 100644
--- a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
+++ b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
@@ -119,40 +119,50 @@ public class ThrownEnderpearl extends ThrowableItemProjectile {
Vec3 vec3 = this.oldPosition();
if (owner instanceof ServerPlayer serverPlayer) {
if (serverPlayer.connection.isAcceptingMessages()) {
- // CraftBukkit start
- ServerPlayer serverPlayer1 = serverPlayer.teleport(new TeleportTransition(serverLevel, vec3, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.ENDER_PEARL));
- if (serverPlayer1 == null) {
- this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT);
- return;
- }
- // CraftBukkit end
- if (this.random.nextFloat() < serverLevel.purpurConfig.enderPearlEndermiteChance && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { // Purpur - Configurable Ender Pearl RNG
- Endermite endermite = EntityType.ENDERMITE.create(serverLevel, EntitySpawnReason.TRIGGERED);
- if (endermite != null) {
- endermite.setPlayerSpawned(true); // Purpur - Add back player spawned endermite API
- endermite.snapTo(owner.getX(), owner.getY(), owner.getZ(), owner.getYRot(), owner.getXRot());
- serverLevel.addFreshEntity(endermite, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.ENDER_PEARL);
+ // DivineMC start - Parallel world ticking
+ java.util.function.Consumer<ServerPlayer> teleportPlayerCrossDimensionTask = taskServerPlayer -> {
+ // CraftBukkit start
+ ServerPlayer serverPlayer1 = serverPlayer.teleport(new TeleportTransition(serverLevel, vec3, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.ENDER_PEARL));
+ if (serverPlayer1 == null) {
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.HIT);
+ return;
+ }
+ // CraftBukkit end
+ if (this.random.nextFloat() < serverLevel.purpurConfig.enderPearlEndermiteChance && serverLevel.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { // Purpur - Configurable Ender Pearl RNG
+ Endermite endermite = EntityType.ENDERMITE.create(serverLevel, EntitySpawnReason.TRIGGERED);
+ if (endermite != null) {
+ endermite.setPlayerSpawned(true); // Purpur - Add back player spawned endermite API
+ endermite.snapTo(owner.getX(), owner.getY(), owner.getZ(), owner.getYRot(), owner.getXRot());
+ serverLevel.addFreshEntity(endermite, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.ENDER_PEARL);
+ }
}
- }
- if (this.isOnPortalCooldown()) {
- owner.setPortalCooldown();
- }
+ if (this.isOnPortalCooldown()) {
+ owner.setPortalCooldown();
+ }
- // CraftBukkit start - moved up
- // ServerPlayer serverPlayer1 = serverPlayer.teleport(
- // new TeleportTransition(
- // serverLevel, vec3, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING
- // )
- // );
- // CraftBukkit end - moved up
- if (serverPlayer1 != null) {
- serverPlayer1.resetFallDistance();
- serverPlayer1.resetCurrentImpulseContext();
- serverPlayer1.hurtServer(serverPlayer.level(), this.damageSources().enderPearl().eventEntityDamager(this), this.level().purpurConfig.enderPearlDamage); // CraftBukkit // Paper - fix DamageSource API // Purpur - Configurable Ender Pearl damage
- }
+ // CraftBukkit start - moved up
+ // ServerPlayer serverPlayer1 = serverPlayer.teleport(
+ // new TeleportTransition(
+ // serverLevel, vec3, Vec3.ZERO, 0.0F, 0.0F, Relative.union(Relative.ROTATION, Relative.DELTA), TeleportTransition.DO_NOTHING
+ // )
+ // );
+ // CraftBukkit end - moved up
+ if (serverPlayer1 != null) {
+ serverPlayer1.resetFallDistance();
+ serverPlayer1.resetCurrentImpulseContext();
+ serverPlayer1.hurtServer(serverPlayer.level(), this.damageSources().enderPearl().eventEntityDamager(this), this.level().purpurConfig.enderPearlDamage); // CraftBukkit // Paper - fix DamageSource API // Purpur - Configurable Ender Pearl damage
+ }
- this.playSound(serverLevel, vec3);
+ this.playSound(serverLevel, vec3);
+ };
+
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) {
+ serverPlayer.getBukkitEntity().taskScheduler.schedule(teleportPlayerCrossDimensionTask, entity -> {}, 0);
+ } else {
+ teleportPlayerCrossDimensionTask.accept(serverPlayer);
+ }
+ // DivineMC end - Parallel world ticking
}
} else {
Entity entity = owner.teleport(
diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java
index bfee8f3f47bc457543022fabbbee2130a6abe81a..55d0b8a88d202fb86c4da84e0d40b63df2461c2a 100644
--- a/net/minecraft/world/inventory/AbstractContainerMenu.java
+++ b/net/minecraft/world/inventory/AbstractContainerMenu.java
@@ -96,8 +96,14 @@ public abstract class AbstractContainerMenu {
public void startOpen() {}
// CraftBukkit end
+ public Throwable containerCreationStacktrace; // DivineMC - Parallel world ticking
protected AbstractContainerMenu(@Nullable MenuType<?> menuType, int containerId) {
+ // DivineMC start - Parallel world ticking (debugging)
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.logContainerCreationStacktraces) {
+ this.containerCreationStacktrace = new Throwable();
+ }
+ // DivineMC start - Parallel world ticking (debugging)
this.menuType = menuType;
this.containerId = containerId;
}
diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java
index b95acf4c688a9113a34e9be2639536245c66c83e..160fd27bdd02e9c276cdae241b0652ae547517cf 100644
--- a/net/minecraft/world/item/ItemStack.java
+++ b/net/minecraft/world/item/ItemStack.java
@@ -398,8 +398,10 @@ 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;
+ // DivineMC start - Parallel world ticking
+ org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.getTreeTypeTL();
+ net.minecraft.world.level.block.SaplingBlock.setTreeTypeTL(null);
+ // DivineMC end - Parallel world ticking
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 bf38e3bfcb0b96c4529d5e535893043512f52b02..dc82e12783989c307be2ac709a21321dac25f217 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -160,6 +160,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files
public final org.bxteam.divinemc.config.DivineWorldConfig divineConfig; // DivineMC - Configuration
+ 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); // DivineMC - 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
@@ -1134,6 +1135,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
@Override
public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft) {
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, pos, "Updating block asynchronously"); // DivineMC - 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
@@ -1517,11 +1519,12 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
this.blockEntityTickers.markAsRemoved(this.tileTickPosition); // DivineMC - optimize block entity removals - Fix MC-117075
} else if (runsNormally && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) {
tickingBlockEntity.tick();
- // Paper start - rewrite chunk system
- if ((++tickedEntities & 7) == 0) {
- ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks();
+ // DivineMC start - Parallel world ticking
+ ++tickedEntities;
+ if (!org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && (tickedEntities & 7) == 0) {
+ this.moonrise$midTickTasks();
}
- // Paper end - rewrite chunk system
+ // DivineMC end - Parallel world ticking
}
}
this.blockEntityTickers.removeMarkedEntries(); // DivineMC - optimize block entity removals - Fix MC-117075
@@ -1541,7 +1544,11 @@ 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
+ // DivineMC start - Parallel world ticking
+ if (!org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) {
+ this.moonrise$midTickTasks(); // Paper - rewrite chunk system
+ }
+ // DivineMC end - Parallel world ticking
}
// Paper start - Option to prevent armor stands from doing entity lookups
@@ -1678,6 +1685,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
@Nullable
@Override
public BlockEntity getBlockEntity(BlockPos pos) {
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThreadOrAsyncThread((ServerLevel) this, "Cannot read world asynchronously"); // DivineMC - 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) {
@@ -1694,6 +1702,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
}
public void setBlockEntity(BlockEntity blockEntity) {
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel) this, "Cannot modify world asynchronously"); // DivineMC - Parallel world ticking
BlockPos blockPos = blockEntity.getBlockPos();
if (!this.isOutsideBuildHeight(blockPos)) {
// CraftBukkit start
@@ -1778,6 +1787,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup<Entity>, AutoCl
@Override
public List<Entity> getEntities(@Nullable Entity entity, AABB boundingBox, Predicate<? super Entity> predicate) {
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread((ServerLevel)this, boundingBox, "Cannot getEntities asynchronously"); // DivineMC - Parallel world ticking (additional concurrency issues logs)
List<Entity> list = Lists.newArrayList();
// Paper start - rewrite chunk system
@@ -2100,8 +2110,15 @@ 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;
+ // DivineMC start - Parallel world ticking
+ int i;
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) {
+ i = this.random.nextInt() >> 2;
+ } else {
+ this.randValue = this.randValue * 3 + 1013904223;
+ i = this.randValue >> 2;
+ }
+ // DivineMC end - 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..ea310d53f728aaf5de3284b372ed393e4225a52f 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.setTreeTypeTL(org.bukkit.TreeType.WARPED_FUNGUS); // DivineMC - Parallel world ticking
} else if (this == Blocks.CRIMSON_FUNGUS) {
- SaplingBlock.treeType = org.bukkit.TreeType.CRIMSON_FUNGUS;
+ SaplingBlock.setTreeTypeTL(org.bukkit.TreeType.CRIMSON_FUNGUS); // DivineMC - 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..6a0c4dc2ff5e3d82e811db63dc9da7b93e6f2cc9 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.setTreeTypeTL((this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM); // CraftBukkit // DivineMC - 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..9585d3164eaa7522be950fe10d8487dcc88e0f0b 100644
--- a/net/minecraft/world/level/block/RedStoneWireBlock.java
+++ b/net/minecraft/world/level/block/RedStoneWireBlock.java
@@ -283,7 +283,13 @@ public class RedStoneWireBlock extends Block {
if (orientation != null) {
source = pos.relative(orientation.getFront().getOpposite());
}
- turbo.updateSurroundingRedstone(worldIn, pos, state, source);
+ // DivineMC start - Parallel world ticking
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) {
+ worldIn.turbo.updateSurroundingRedstone(worldIn, pos, state, source);
+ } else {
+ turbo.updateSurroundingRedstone(worldIn, pos, state, source);
+ }
+ // DivineMC end
return;
}
updatePowerStrength(worldIn, pos, state, orientation, blockAdded);
@@ -311,7 +317,13 @@ 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);
+ // DivineMC start - Parallel world ticking
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) {
+ level.turbo.updateNeighborShapes(level, pos, state);
+ } else {
+ turbo.updateNeighborShapes(level, pos, state);
+ }
+ // DivineMC end - Parallel world ticking
}
}
}
diff --git a/net/minecraft/world/level/block/SaplingBlock.java b/net/minecraft/world/level/block/SaplingBlock.java
index a22cb810622e0ae97bc2a0d6390d026d9482b783..5856178e41523700ca7ed9a46c1c802c33903b53 100644
--- a/net/minecraft/world/level/block/SaplingBlock.java
+++ b/net/minecraft/world/level/block/SaplingBlock.java
@@ -26,6 +26,26 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock {
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
+ // DivineMC start - Parallel world ticking
+ public static final ThreadLocal<org.bukkit.TreeType> treeTypeTL = new ThreadLocal<>();
+
+ public static org.bukkit.TreeType getTreeTypeTL() {
+ org.bukkit.TreeType treeTypeRTCopy;
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && (treeTypeRTCopy = treeTypeTL.get()) != null) return treeTypeRTCopy;
+
+ synchronized (SaplingBlock.class) {
+ return treeType;
+ }
+ }
+
+ public static void setTreeTypeTL(org.bukkit.TreeType value) {
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) treeTypeTL.set(value);
+
+ synchronized (SaplingBlock.class) {
+ treeType = value;
+ }
+ }
+ // DivineMC end - Parallel world ticking
@Override
public MapCodec<? extends SaplingBlock> codec() {
@@ -62,14 +82,16 @@ 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;
+ // DivineMC start - Parallel world ticking
+ org.bukkit.TreeType treeTypeLocal = SaplingBlock.getTreeTypeTL();
+ SaplingBlock.setTreeTypeTL(null);
+ // DivineMC end - Parallel world ticking
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();
org.bukkit.event.world.StructureGrowEvent event = null;
- if (treeType != null) {
- event = new org.bukkit.event.world.StructureGrowEvent(location, treeType, false, null, blocks);
+ if (treeTypeLocal != null) { // DivineMC - Parallel world ticking
+ event = new org.bukkit.event.world.StructureGrowEvent(location, treeTypeLocal, false, null, blocks); // DivineMC - Parallel world ticking
org.bukkit.Bukkit.getPluginManager().callEvent(event);
}
if (event == null || !event.isCancelled()) {
diff --git a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
index 5a094257a31f0500278a706a418e1697f8810ffb..3df0633fe4e632f7d42289facf4ad79978d50c40 100644
--- a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
@@ -76,6 +76,12 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co
}
public static boolean canUnlock(Player player, LockCode code, Component displayName, @Nullable BlockEntity blockEntity) {
+ // DivineMC start - Parallel world ticking
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking && player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != serverPlayer.level()) {
+ net.minecraft.server.MinecraftServer.LOGGER.warn("Player {} ({}) attempted to open a BlockEntity @ {} {}, {}, {} while they were in a different world {} than the block themselves!", serverPlayer.getScoreboardName(), serverPlayer.getStringUUID(), blockEntity.getLevel().getWorld().getName(), blockEntity.getBlockPos().getX(), blockEntity.getBlockPos().getY(), blockEntity.getBlockPos().getZ(), serverPlayer.level().getWorld().getName());
+ return false;
+ }
+ // DivineMC end - Parallel world ticking
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 0a94670dc20bb9c521b0395633eb100393895f6a..4ad9c47862a9791f72a18835a343bf0e962c14c8 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..92e9bc9ba577474ca1108b8d0615739502ca5e57 100644
--- a/net/minecraft/world/level/block/grower/TreeGrower.java
+++ b/net/minecraft/world/level/block/grower/TreeGrower.java
@@ -203,55 +203,59 @@ public final class TreeGrower {
// CraftBukkit start
private void setTreeType(Holder<ConfiguredFeature<?, ?>> feature) {
+ // DivineMC start - Parallel world ticking
+ org.bukkit.TreeType treeType;
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;
} 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;
} 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;
} 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;
} 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;
} else if (feature.is(TreeFeatures.PINE)) {
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_REDWOOD;
+ treeType = org.bukkit.TreeType.TALL_REDWOOD;
} else if (feature.is(TreeFeatures.SPRUCE)) {
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.REDWOOD;
+ treeType = org.bukkit.TreeType.REDWOOD;
} else if (feature.is(TreeFeatures.ACACIA)) {
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.ACACIA;
+ treeType = org.bukkit.TreeType.ACACIA;
} 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;
} 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;
} else if (feature.is(TreeFeatures.SWAMP_OAK)) {
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SWAMP;
+ treeType = org.bukkit.TreeType.SWAMP;
} 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;
} 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;
} 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;
} 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;
} 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;
} else if (feature.is(TreeFeatures.MEGA_JUNGLE_TREE)) {
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE;
+ treeType = org.bukkit.TreeType.JUNGLE;
} else if (feature.is(TreeFeatures.AZALEA_TREE)) {
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.AZALEA;
+ treeType = org.bukkit.TreeType.AZALEA;
} else if (feature.is(TreeFeatures.MANGROVE)) {
- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MANGROVE;
+ treeType = org.bukkit.TreeType.MANGROVE;
} 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;
} 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;
} 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;
} 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;
} else {
throw new IllegalArgumentException("Unknown tree generator " + feature);
}
+ net.minecraft.world.level.block.SaplingBlock.setTreeTypeTL(treeType);
+ // DivineMC end - Parallel world ticking
}
// CraftBukkit end
}
diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
index 010c7204bfa772ff9a2187a5db76d7c65ea34f66..6de61b3e75edee87562e3aab01c7020cd91133b3 100644
--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -365,6 +365,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p
@Nullable
@Override
public BlockState setBlockState(BlockPos pos, BlockState state, int flags) {
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(this.level, pos, "Updating block asynchronously"); // DivineMC - Parallel world ticking
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 9e75320e51886e0f93c23683d8614128f44a613e..df8ede5c6d1f1af275f813d4b939136134214dff 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 {
public final java.util.concurrent.ConcurrentLinkedQueue<Entity> entities = new java.util.concurrent.ConcurrentLinkedQueue<>(); // Paper - rewrite chunk system // DivineMC - Async mob spawning
+ // DivineMC start - Parallel world ticking
+ private final net.minecraft.server.level.ServerLevel serverLevel;
+
+ public EntityTickList(net.minecraft.server.level.ServerLevel serverLevel) {
+ this.serverLevel = serverLevel;
+ }
+ // DivineMC end - Parallel world ticking
private void ensureActiveIsNotIterated() {
// Paper - rewrite chunk system
}
public void add(Entity entity) {
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist addition"); // DivineMC - Parallel world ticking
this.ensureActiveIsNotIterated();
this.entities.add(entity); // Paper - rewrite chunk system
}
public void remove(Entity entity) {
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(entity, "Asynchronous entity ticklist removal"); // DivineMC - Parallel world ticking
this.ensureActiveIsNotIterated();
this.entities.remove(entity); // Paper - rewrite chunk system
}
@@ -30,6 +39,7 @@ public class EntityTickList {
}
public void forEach(Consumer<Entity> entity) {
+ if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableParallelWorldTicking) ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread(serverLevel, "Asynchronous entity ticklist iteration"); // DivineMC - Parallel world ticking
// 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)