From 093b1e53943520f39d84b66e135a0f673899ef94 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Wed, 1 Mar 2023 19:12:31 -0800 Subject: [PATCH] Fix several issues, mostly saving pending teleporting entities The place/portal async function now track entities that have been removed from the world but have not teleported. When the server shuts down, these entities will have their passenger tree restored and re-added to the entity slices at the location they were teleporting to, or in the case of portals that did not run placeAsync yet, the location they entered the portal on. This should ensure that for regular teleports that the entity is placed at its correct target location, and for portalling to ensure that either the entity is placed at the portal entrace location (where they entered) or the portal destination. In any case, the entity is preserved in a location and will survive the shutdown. Additionally, move player saving until after the worlds save. This is to ensure that the save logic is performed only after all teleportations have completed. Fix some other misc issues as well: - Fix double nether portal creation by checking if a portal exists again before creating it, fixing a race condition where two entites would portal and neither would see that the other created a portal. - Make all remove ticket add an unknown ticket. In general this behavior is better since it means that unloads will only ever occur at the next tick, rather than during the tick logic. Thus, there will be no cases where a chunk is unloaded unexpectedly. - Do not use fastFloor for calculating chunk position from block position It is not going to return a good value outside of [-1024, 1024] - Always perform mid tick update for ticking regionised player chunk loader If no entities were loaded, no chunks were loaded, and nothing else - the logic would not have otherwise ran. This fixed some rare cases of chunks never loading for players after logging in. --- .../0002-New-player-chunk-loader-system.patch | 6 +- patches/server/0004-Threaded-Regions.patch | 476 ++++++++++++------ ...lism-for-neighbour-writing-chunk-sta.patch | 8 +- regiontodo.txt | 1 - 4 files changed, 339 insertions(+), 152 deletions(-) diff --git a/patches/server/0002-New-player-chunk-loader-system.patch b/patches/server/0002-New-player-chunk-loader-system.patch index 2057ef8..3c8dfe6 100644 --- a/patches/server/0002-New-player-chunk-loader-system.patch +++ b/patches/server/0002-New-player-chunk-loader-system.patch @@ -46,10 +46,10 @@ index 0e45a340ae534caf676b7f9d0adcbcee5829925e..6df1948b1204a7288ecb7238b6fc2a73 private ChunkSystem() { diff --git a/src/main/java/io/papermc/paper/chunk/system/RegionisedPlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/system/RegionisedPlayerChunkLoader.java new file mode 100644 -index 0000000000000000000000000000000000000000..cb170d1039fd9dadfbc27da0b181c00742e72025 +index 0000000000000000000000000000000000000000..7e2176f343160b299e7d4a2817c8f6c9ba7dba7b --- /dev/null +++ b/src/main/java/io/papermc/paper/chunk/system/RegionisedPlayerChunkLoader.java -@@ -0,0 +1,1302 @@ +@@ -0,0 +1,1304 @@ +package io.papermc.paper.chunk.system; + +import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; @@ -283,8 +283,10 @@ index 0000000000000000000000000000000000000000..cb170d1039fd9dadfbc27da0b181c007 + + public void tick() { + TickThread.ensureTickThread("Cannot tick player chunk loader async"); ++ long currTime = System.nanoTime(); + for (final ServerPlayer player : this.world.players()) { + player.chunkLoader.update(); ++ player.chunkLoader.midTickUpdate(currTime); + } + } + diff --git a/patches/server/0004-Threaded-Regions.patch b/patches/server/0004-Threaded-Regions.patch index 9cecf64..b031455 100644 --- a/patches/server/0004-Threaded-Regions.patch +++ b/patches/server/0004-Threaded-Regions.patch @@ -2133,18 +2133,19 @@ index 6df1948b1204a7288ecb7238b6fc2a733f7d25b3..6a413abc67aa4dcbab64231be3eb1344 public static ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) { diff --git a/src/main/java/io/papermc/paper/chunk/system/RegionisedPlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/system/RegionisedPlayerChunkLoader.java -index cb170d1039fd9dadfbc27da0b181c00742e72025..5cccfcf45b3c3cdfdebdf47dc674934441cc0c4c 100644 +index 7e2176f343160b299e7d4a2817c8f6c9ba7dba7b..245242b276e3de1edde1e2ebd0ce518fd0d08117 100644 --- a/src/main/java/io/papermc/paper/chunk/system/RegionisedPlayerChunkLoader.java +++ b/src/main/java/io/papermc/paper/chunk/system/RegionisedPlayerChunkLoader.java -@@ -231,14 +231,14 @@ public class RegionisedPlayerChunkLoader { - +@@ -232,7 +232,7 @@ public class RegionisedPlayerChunkLoader { public void tick() { TickThread.ensureTickThread("Cannot tick player chunk loader async"); + long currTime = System.nanoTime(); - for (final ServerPlayer player : this.world.players()) { + for (final ServerPlayer player : this.world.getLocalPlayers()) { // Folia - region threding player.chunkLoader.update(); + player.chunkLoader.midTickUpdate(currTime); } - } +@@ -240,7 +240,7 @@ public class RegionisedPlayerChunkLoader { public void tickMidTick() { final long time = System.nanoTime(); @@ -2154,7 +2155,7 @@ index cb170d1039fd9dadfbc27da0b181c00742e72025..5cccfcf45b3c3cdfdebdf47dc6749344 } } diff --git a/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java -index 61c170555c8854b102c640b0b6a615f9f732edbf..687fc3e7ada3da9c5b938b0ffb9e8bcf90c2d1c7 100644 +index 61c170555c8854b102c640b0b6a615f9f732edbf..515cc130a411f218ed20628eb918be9d770b9939 100644 --- a/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java +++ b/src/main/java/io/papermc/paper/chunk/system/entity/EntityLookup.java @@ -187,7 +187,12 @@ public final class EntityLookup implements LevelEntityGetter { @@ -2191,7 +2192,7 @@ index 61c170555c8854b102c640b0b6a615f9f732edbf..687fc3e7ada3da9c5b938b0ffb9e8bcf EntityLookup.this.worldCallback.onTrackingEnd(entity); } } -@@ -385,6 +394,8 @@ public final class EntityLookup implements LevelEntityGetter { +@@ -385,11 +394,26 @@ public final class EntityLookup implements LevelEntityGetter { entity.setLevelCallback(new EntityCallback(entity)); @@ -2200,7 +2201,25 @@ index 61c170555c8854b102c640b0b6a615f9f732edbf..687fc3e7ada3da9c5b938b0ffb9e8bcf this.entityStatusChange(entity, slices, Visibility.HIDDEN, getEntityStatus(entity), false, !fromDisk, false); return true; -@@ -407,6 +418,7 @@ public final class EntityLookup implements LevelEntityGetter { + } + ++ // Folia start - region threading ++ // only appropriate to use when in shutdown, as this performs no logic hooks to properly add to world ++ public boolean addEntityForShutdownTeleportComplete(final Entity entity) { ++ final BlockPos pos = entity.blockPosition(); ++ final int sectionX = pos.getX() >> 4; ++ final int sectionY = Mth.clamp(pos.getY() >> 4, this.minSection, this.maxSection); ++ final int sectionZ = pos.getZ() >> 4; ++ final ChunkEntitySlices slices = this.getOrCreateChunk(sectionX, sectionZ); ++ ++ return slices.addEntity(entity, sectionY); ++ } ++ // Folia end - region threading ++ + private void removeEntity(final Entity entity) { + final int sectionX = entity.sectionX; + final int sectionY = entity.sectionY; +@@ -407,6 +431,7 @@ public final class EntityLookup implements LevelEntityGetter { LOGGER.warn("Failed to remove entity " + entity + " from entity slices (" + sectionX + "," + sectionZ + ")"); } } @@ -2208,7 +2227,7 @@ index 61c170555c8854b102c640b0b6a615f9f732edbf..687fc3e7ada3da9c5b938b0ffb9e8bcf entity.sectionX = entity.sectionY = entity.sectionZ = Integer.MIN_VALUE; this.entityByLock.writeLock(); -@@ -823,6 +835,9 @@ public final class EntityLookup implements LevelEntityGetter { +@@ -823,6 +848,9 @@ public final class EntityLookup implements LevelEntityGetter { EntityLookup.this.entityStatusChange(entity, null, tickingState, Visibility.HIDDEN, false, false, reason.shouldDestroy()); this.entity.setLevelCallback(NoOpCallback.INSTANCE); @@ -2219,7 +2238,7 @@ index 61c170555c8854b102c640b0b6a615f9f732edbf..687fc3e7ada3da9c5b938b0ffb9e8bcf } diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java -index c6d20bc2f0eab737338db6b88dacb63f0decb66c..309b45885edc1400ae5a97cac7e5e5a19d73be0c 100644 +index c6d20bc2f0eab737338db6b88dacb63f0decb66c..32b88d7902e877e1cce0b7635cbfa67b84b8eac0 100644 --- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java +++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java @@ -3,7 +3,6 @@ package io.papermc.paper.chunk.system.scheduling; @@ -2371,9 +2390,8 @@ index c6d20bc2f0eab737338db6b88dacb63f0decb66c..309b45885edc1400ae5a97cac7e5e5a1 + + // add them all + into.specialCaseUnload.addAll(this.specialCaseUnload); - } - -- final int saveTickCompare = Long.compare(c1.lastAutoSave, c2.lastAutoSave); ++ } ++ + public void split(final int chunkToRegionShift, final Long2ReferenceOpenHashMap regionToData, + final ReferenceOpenHashSet dataSet) { + for (final NewChunkHolder fullLoadUpdate : this.pendingFullLoadUpdate) { @@ -2385,9 +2403,7 @@ index c6d20bc2f0eab737338db6b88dacb63f0decb66c..309b45885edc1400ae5a97cac7e5e5a1 + data.pendingFullLoadUpdate.add(fullLoadUpdate); + } // else: fullLoadUpdate is an unloaded chunk holder + } - -- if (saveTickCompare != 0) { -- return saveTickCompare; ++ + for (final NewChunkHolder autoSave : this.autoSaveQueue) { + final int regionCoordinateX = autoSave.chunkX >> chunkToRegionShift; + final int regionCoordinateZ = autoSave.chunkZ >> chunkToRegionShift; @@ -2441,28 +2457,29 @@ index c6d20bc2f0eab737338db6b88dacb63f0decb66c..309b45885edc1400ae5a97cac7e5e5a1 + } } + } - -- final long coord1 = CoordinateUtils.getChunkKey(c1.chunkX, c1.chunkZ); -- final long coord2 = CoordinateUtils.getChunkKey(c2.chunkX, c2.chunkZ); ++ + private ChunkHolderManager.HolderManagerRegionData getCurrentRegionData() { + final ThreadedRegioniser.ThreadedRegion region = + TickRegionScheduler.getCurrentRegion(); -- if (coord1 == coord2) { -- throw new IllegalStateException("Duplicate chunkholder in auto save queue"); +- final int saveTickCompare = Long.compare(c1.lastAutoSave, c2.lastAutoSave); + if (region == null) { + return null; - } ++ } -- return Long.compare(coord1, coord2); -- }); +- if (saveTickCompare != 0) { +- return saveTickCompare; + if (this.world != null && this.world != region.getData().world) { + throw new IllegalStateException("World check failed: expected world: " + this.world.getWorld().getKey() + ", region world: " + region.getData().world.getWorld().getKey()); -+ } -+ + } + +- final long coord1 = CoordinateUtils.getChunkKey(c1.chunkX, c1.chunkZ); +- final long coord2 = CoordinateUtils.getChunkKey(c2.chunkX, c2.chunkZ); + return region.getData().getHolderManagerRegionData(); + } -+ + +- if (coord1 == coord2) { +- throw new IllegalStateException("Duplicate chunkholder in auto save queue"); + // MUST hold ticket lock + private ChunkHolderManager.HolderManagerRegionData getDataFor(final long key) { + return this.getDataFor(CoordinateUtils.getChunkX(key), CoordinateUtils.getChunkZ(key)); @@ -2472,8 +2489,10 @@ index c6d20bc2f0eab737338db6b88dacb63f0decb66c..309b45885edc1400ae5a97cac7e5e5a1 + private ChunkHolderManager.HolderManagerRegionData getDataFor(final int chunkX, final int chunkZ) { + if (!this.ticketLock.isHeldByCurrentThread()) { + throw new IllegalStateException("Must hold ticket level lock"); -+ } -+ + } + +- return Long.compare(coord1, coord2); +- }); + final ThreadedRegioniser.ThreadedRegion region + = this.world.regioniser.getRegionAtUnsynchronised(chunkX, chunkZ); + @@ -2727,20 +2746,14 @@ index c6d20bc2f0eab737338db6b88dacb63f0decb66c..309b45885edc1400ae5a97cac7e5e5a1 return new Long2IntOpenHashMap(); }).addTo(chunk, 1); } -@@ -439,35 +659,43 @@ public final class ChunkHolderManager { - return false; - } +@@ -441,33 +661,37 @@ public final class ChunkHolderManager { -+ final ChunkHolderManager.HolderManagerRegionData currRegionData = this.getCurrentRegionData(); // Folia - region threading -+ this.ticketLock.lock(); try { - final SortedArraySet> ticketsAtChunk = this.tickets.get(chunk); + // Folia start - region threading + final ChunkHolderManager.HolderManagerRegionData targetData = this.getDataFor(chunk); + -+ final boolean sameRegion = currRegionData == targetData; -+ + final SortedArraySet> ticketsAtChunk = targetData == null ? null : targetData.tickets.get(chunk); + // Folia end - region threading if (ticketsAtChunk == null) { @@ -2776,13 +2789,13 @@ index c6d20bc2f0eab737338db6b88dacb63f0decb66c..309b45885edc1400ae5a97cac7e5e5a1 } } } -@@ -476,6 +704,13 @@ public final class ChunkHolderManager { +@@ -476,6 +700,13 @@ public final class ChunkHolderManager { this.updateTicketLevel(chunk, newLevel); } + // Folia start - region threading -+ // if we're not the target region, we should not change the ticket levels while the target region may be ticking -+ if (!sameRegion && newLevel > level) { ++ // we should not change the ticket levels while the target region may be ticking ++ if (newLevel > level) { + this.addTicketAtLevel(TicketType.UNKNOWN, chunk, level, new ChunkPos(chunk)); + } + // Folia end - region threading @@ -2790,7 +2803,7 @@ index c6d20bc2f0eab737338db6b88dacb63f0decb66c..309b45885edc1400ae5a97cac7e5e5a1 return true; } finally { this.ticketLock.unlock(); -@@ -516,24 +751,33 @@ public final class ChunkHolderManager { +@@ -516,24 +747,33 @@ public final class ChunkHolderManager { this.ticketLock.lock(); try { @@ -2831,7 +2844,7 @@ index c6d20bc2f0eab737338db6b88dacb63f0decb66c..309b45885edc1400ae5a97cac7e5e5a1 if (toRemove == null) { return; -@@ -546,10 +790,10 @@ public final class ChunkHolderManager { +@@ -546,10 +786,10 @@ public final class ChunkHolderManager { for (final LongIterator iterator = toRemove.keySet().longIterator(); iterator.hasNext();) { final long chunk = iterator.nextLong(); @@ -2844,7 +2857,7 @@ index c6d20bc2f0eab737338db6b88dacb63f0decb66c..309b45885edc1400ae5a97cac7e5e5a1 this.ticketLevelPropagator.removeSource(chunk); } else { this.ticketLevelPropagator.setSource(chunk, convertBetweenTicketLevels(tickets.first().getTicketLevel())); -@@ -798,30 +1042,62 @@ public final class ChunkHolderManager { +@@ -798,30 +1038,62 @@ public final class ChunkHolderManager { if (changedFullStatus.isEmpty()) { return; } @@ -2920,7 +2933,7 @@ index c6d20bc2f0eab737338db6b88dacb63f0decb66c..309b45885edc1400ae5a97cac7e5e5a1 this.chunkHolders.remove(CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ)); } -@@ -839,23 +1115,42 @@ public final class ChunkHolderManager { +@@ -839,23 +1111,42 @@ public final class ChunkHolderManager { throw new IllegalStateException("Cannot hold scheduling lock while calling processUnloads"); } @@ -2967,7 +2980,7 @@ index c6d20bc2f0eab737338db6b88dacb63f0decb66c..309b45885edc1400ae5a97cac7e5e5a1 if (chunkHolder.isSafeToUnload() != null) { LOGGER.error("Chunkholder " + chunkHolder + " is not safe to unload but is inside the unload queue?"); continue; -@@ -1193,7 +1488,12 @@ public final class ChunkHolderManager { +@@ -1193,7 +1484,12 @@ public final class ChunkHolderManager { // only call on tick thread protected final boolean processPendingFullUpdate() { @@ -2981,7 +2994,7 @@ index c6d20bc2f0eab737338db6b88dacb63f0decb66c..309b45885edc1400ae5a97cac7e5e5a1 boolean ret = false; -@@ -1204,9 +1504,7 @@ public final class ChunkHolderManager { +@@ -1204,9 +1500,7 @@ public final class ChunkHolderManager { ret |= holder.handleFullStatusChange(changedFullStatus); if (!changedFullStatus.isEmpty()) { @@ -2992,7 +3005,7 @@ index c6d20bc2f0eab737338db6b88dacb63f0decb66c..309b45885edc1400ae5a97cac7e5e5a1 changedFullStatus.clear(); } } -@@ -1256,7 +1554,7 @@ public final class ChunkHolderManager { +@@ -1256,7 +1550,7 @@ public final class ChunkHolderManager { private JsonObject getDebugJsonNoLock() { final JsonObject ret = new JsonObject(); @@ -3001,7 +3014,7 @@ index c6d20bc2f0eab737338db6b88dacb63f0decb66c..309b45885edc1400ae5a97cac7e5e5a1 final JsonArray unloadQueue = new JsonArray(); ret.add("unload_queue", unloadQueue); -@@ -1275,60 +1573,73 @@ public final class ChunkHolderManager { +@@ -1275,60 +1569,73 @@ public final class ChunkHolderManager { holders.add(holder.getDebugJson()); } @@ -3719,16 +3732,17 @@ index 0000000000000000000000000000000000000000..d9687722e02dfd4088c7030abbf5008e +} diff --git a/src/main/java/io/papermc/paper/threadedregions/RegionShutdownThread.java b/src/main/java/io/papermc/paper/threadedregions/RegionShutdownThread.java new file mode 100644 -index 0000000000000000000000000000000000000000..70c3accbab4e69268435c6f4fb13d29c7662283d +index 0000000000000000000000000000000000000000..362e85df5e4483608ab4a6192acd8bc499e8c9bd --- /dev/null +++ b/src/main/java/io/papermc/paper/threadedregions/RegionShutdownThread.java -@@ -0,0 +1,112 @@ +@@ -0,0 +1,157 @@ +package io.papermc.paper.threadedregions; + +import com.mojang.logging.LogUtils; +import io.papermc.paper.util.TickThread; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; ++import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ChunkPos; +import org.slf4j.Logger; +import java.util.ArrayList; @@ -3781,6 +3795,40 @@ index 0000000000000000000000000000000000000000..70c3accbab4e69268435c6f4fb13d29c + } + } + ++ private void finishTeleportations(final ThreadedRegioniser.ThreadedRegion region, ++ final ServerLevel world) { ++ final List pendingTeleports = world.removeAllRegionTeleports(); ++ if (pendingTeleports.isEmpty()) { ++ return; ++ } ++ final ChunkPos center = region.getCenterChunk(); ++ LOGGER.info("Completing " + pendingTeleports.size() + " pending teleports in region around chunk " + center + " in world '" + region.regioniser.world.getWorld().getName() + "'"); ++ try { ++ this.shuttingDown = region; ++ for (final ServerLevel.PendingTeleport pendingTeleport : pendingTeleports) { ++ LOGGER.info("Completing teleportation to target position " + pendingTeleport.to()); ++ ++ // first, add entities to entity chunk so that they will be saved ++ for (final Entity.EntityTreeNode node : pendingTeleport.rootVehicle().getFullTree()) { ++ // assume that world and position are set to destination here ++ node.root.level = world; // in case the pending teleport is from a portal before it finds the exact destination ++ world.getEntityLookup().addEntityForShutdownTeleportComplete(node.root); ++ } ++ ++ // then, rebuild the passenger tree so that when saving only the root vehicle will be written - and if ++ // there are any player passengers, that the later player saving will save the tree ++ pendingTeleport.rootVehicle().restore(); ++ ++ // now we are finished ++ LOGGER.info("Completed teleportation to target position " + pendingTeleport.to()); ++ } ++ } catch (final Throwable thr) { ++ LOGGER.error("Failed to complete pending teleports", thr); ++ } finally { ++ this.shuttingDown = null; ++ } ++ } ++ + private void saveRegionChunks(final ThreadedRegioniser.ThreadedRegion region, + final boolean first, final boolean last) { + final ChunkPos center = region.getCenterChunk(); @@ -3821,6 +3869,11 @@ index 0000000000000000000000000000000000000000..70c3accbab4e69268435c6f4fb13d29c + + for (int i = 0, len = regions.size(); i < len; ++i) { + final ThreadedRegioniser.ThreadedRegion region = regions.get(i); ++ this.finishTeleportations(region, world); ++ } ++ ++ for (int i = 0, len = regions.size(); i < len; ++i) { ++ final ThreadedRegioniser.ThreadedRegion region = regions.get(i); + this.saveRegionChunks(region, i == 0, (i + 1) == len); + } + @@ -3831,6 +3884,11 @@ index 0000000000000000000000000000000000000000..70c3accbab4e69268435c6f4fb13d29c + + this.saveLevelData(world); + } ++ // moved from stop part 1 ++ // we need this to be after saving level data, as that will complete any teleportations the player is in ++ LOGGER.info("Saving players"); ++ MinecraftServer.getServer().getPlayerList().saveAll(); ++ + MinecraftServer.getServer().stopPart2(); // stop part 2: close other resources (io thread, etc) + // done, part 2 should call exit() + } @@ -9367,7 +9425,7 @@ index e08f4e39db4ee3fed62e37364d17dcc5c5683504..03d239460a2e856c1f59d6bcd95811c8 } } diff --git a/src/main/java/io/papermc/paper/util/CoordinateUtils.java b/src/main/java/io/papermc/paper/util/CoordinateUtils.java -index 413e4b6da027876dbbe8eb78f2568a440f431547..d29a4a3bab456df99fbccddc832a9ac2da880f31 100644 +index 413e4b6da027876dbbe8eb78f2568a440f431547..3a7dbcb9964723b8ed5e6b0a1ee4267923c746e4 100644 --- a/src/main/java/io/papermc/paper/util/CoordinateUtils.java +++ b/src/main/java/io/papermc/paper/util/CoordinateUtils.java @@ -5,6 +5,7 @@ import net.minecraft.core.SectionPos; @@ -9384,27 +9442,27 @@ index 413e4b6da027876dbbe8eb78f2568a440f431547..d29a4a3bab456df99fbccddc832a9ac2 + // TODO rebase + public static int getBlockX(final Vec3 pos) { -+ return Mth.fastFloor(pos.x); ++ return Mth.floor(pos.x); + } + + public static int getBlockY(final Vec3 pos) { -+ return Mth.fastFloor(pos.y); ++ return Mth.floor(pos.y); + } + + public static int getBlockZ(final Vec3 pos) { -+ return Mth.fastFloor(pos.z); ++ return Mth.floor(pos.z); + } + + public static int getChunkX(final Vec3 pos) { -+ return Mth.fastFloor(pos.x) >> 4; ++ return Mth.floor(pos.x) >> 4; + } + + public static int getChunkY(final Vec3 pos) { -+ return Mth.fastFloor(pos.y) >> 4; ++ return Mth.floor(pos.y) >> 4; + } + + public static int getChunkZ(final Vec3 pos) { -+ return Mth.fastFloor(pos.z) >> 4; ++ return Mth.floor(pos.z) >> 4; + } + private CoordinateUtils() { @@ -10554,7 +10612,7 @@ index 27d4aa45e585842c04491839826d405d6f447f0e..e6ef0691588fbb33d47692db4269c565 // CraftBukkit start - SPIGOT-5477, MC-142590 } else if (MinecraftServer.getServer().hasStopped() || (listener instanceof ServerGamePacketListenerImpl && ((ServerGamePacketListenerImpl) listener).processedDisconnect)) { diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 2ee4e5e8d17a3a1e6a342c74b13135df030ffef6..9577b633ecf5ebd1ff5bf79aa6ea61160f59e764 100644 +index 2ee4e5e8d17a3a1e6a342c74b13135df030ffef6..53ae4a0a57f5f771c6ade3e26ab792162cf8e5cb 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -291,7 +291,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop getNearbyPlayers(net.minecraft.world.entity.ai.targeting.TargetingConditions condition, LivingEntity source, AABB axisalignedbb) { - com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearby; @@ -13719,9 +13784,11 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 - if (nearby == null) { - return ret; - } -- ++ // Folia - region threading + - Object[] backingSet = nearby.getBackingSet(); -- ++ // Folia - region threading + - for (int i = 0, len = backingSet.length; i < len; ++i) { - Object _player = backingSet[i]; - if (!(_player instanceof ServerPlayer)) { @@ -13733,15 +13800,14 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 - ret.add(player); - } - } -+ // Folia - region threading - +- - return ret; - } + // Folia - region threading // Paper end - optimise get nearest players for entity AI public final io.papermc.paper.chunk.system.RegionisedPlayerChunkLoader playerChunkLoader = new io.papermc.paper.chunk.system.RegionisedPlayerChunkLoader(this); -@@ -565,6 +533,29 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -565,6 +533,59 @@ public class ServerLevel extends Level implements WorldGenLevel { }); } @@ -13766,12 +13832,42 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 + public final java.util.concurrent.atomic.AtomicInteger checkInitialised = new java.util.concurrent.atomic.AtomicInteger(WORLD_INIT_NOT_CHECKED); + public ChunkPos randomSpawnSelection; + ++ public static final record PendingTeleport(Entity.EntityTreeNode rootVehicle, Vec3 to) {} ++ private final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet pendingTeleports = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); ++ ++ public void pushPendingTeleport(final PendingTeleport teleport) { ++ synchronized (this.pendingTeleports) { ++ this.pendingTeleports.add(teleport); ++ } ++ } ++ ++ public boolean removePendingTeleport(final PendingTeleport teleport) { ++ synchronized (this.pendingTeleports) { ++ return this.pendingTeleports.remove(teleport); ++ } ++ } ++ ++ public List removeAllRegionTeleports() { ++ final List ret = new ArrayList<>(); ++ ++ synchronized (this.pendingTeleports) { ++ for (final Iterator iterator = this.pendingTeleports.iterator(); iterator.hasNext();) { ++ final PendingTeleport pendingTeleport = iterator.next(); ++ if (io.papermc.paper.util.TickThread.isTickThreadFor(this, pendingTeleport.to())) { ++ ret.add(pendingTeleport); ++ iterator.remove(); ++ } ++ } ++ } ++ ++ return ret; ++ } + // Folia end - regionised ticking + // Add env and gen to constructor, IWorldDataServer -> WorldDataServer public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { // Holder holder = worlddimension.type(); // CraftBukkit - decompile error -@@ -574,13 +565,13 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -574,13 +595,13 @@ public class ServerLevel extends Level implements WorldGenLevel { this.convertable = convertable_conversionsession; this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile()); // CraftBukkit end @@ -13791,7 +13887,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 this.dragonParts = new Int2ObjectOpenHashMap(); this.tickTime = flag1; this.server = minecraftserver; -@@ -619,7 +610,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -619,7 +640,7 @@ public class ServerLevel extends Level implements WorldGenLevel { }); this.chunkSource.getGeneratorState().ensureStructuresGenerated(); this.portalForcer = new PortalForcer(this); @@ -13800,23 +13896,22 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 this.prepareWeather(); this.getWorldBorder().setAbsoluteMaxSize(minecraftserver.getAbsoluteMaxWorldSize()); this.raids = (Raids) this.getDataStorage().computeIfAbsent((nbttagcompound) -> { -@@ -647,8 +638,15 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -647,7 +668,14 @@ public class ServerLevel extends Level implements WorldGenLevel { this.chunkTaskScheduler = new io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler(this, io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.workerThreads); // Paper - rewrite chunk system this.entityLookup = new io.papermc.paper.chunk.system.entity.EntityLookup(this, new EntityCallbacks()); // Paper - rewrite chunk system + this.updateTickData(); // Folia - region threading - make sure it is initialised before ticked - } - ++ } ++ + // Folia start - region threading + public void updateTickData() { + this.tickData = new io.papermc.paper.threadedregions.RegionisedServer.WorldLevelData(this, this.serverLevelData.getGameTime(), this.serverLevelData.getDayTime()); -+ } + } + // Folia end - region threading -+ + public void setWeatherParameters(int clearDuration, int rainDuration, boolean raining, boolean thundering) { this.serverLevelData.setClearWeatherTime(clearDuration); - this.serverLevelData.setRainTime(rainDuration); -@@ -666,55 +664,31 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -666,55 +694,31 @@ public class ServerLevel extends Level implements WorldGenLevel { return this.structureManager; } @@ -13856,8 +13951,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 - this.setDayTime(this.getDayTime() + event.getSkipAmount()); - } - } -+ if (region == null) this.tickSleep(); // Folia - region threading - +- - if (!event.isCancelled()) { - this.wakeUpAllPlayers(); - } @@ -13866,7 +13960,8 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 - this.resetWeatherCycle(); - } - } -- ++ if (region == null) this.tickSleep(); // Folia - region threading + - this.updateSkyBrightness(); + if (region == null) this.updateSkyBrightness(); // Folia - region threading this.tickTime(); @@ -13884,7 +13979,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 gameprofilerfiller.pop(); } timings.scheduledBlocks.stopTiming(); // Paper -@@ -731,7 +705,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -731,7 +735,7 @@ public class ServerLevel extends Level implements WorldGenLevel { timings.doSounds.startTiming(); // Spigot this.runBlockEvents(); timings.doSounds.stopTiming(); // Spigot @@ -13893,7 +13988,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 gameprofilerfiller.pop(); boolean flag = true || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players -@@ -743,20 +717,30 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -743,20 +747,30 @@ public class ServerLevel extends Level implements WorldGenLevel { gameprofilerfiller.push("entities"); timings.tickEntities.startTiming(); // Spigot if (this.dragonFight != null) { @@ -13925,7 +14020,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 gameprofilerfiller.pop(); if (true || this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) { // Paper - now always true if in the ticking list Entity entity1 = entity.getVehicle(); -@@ -787,6 +771,31 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -787,6 +801,31 @@ public class ServerLevel extends Level implements WorldGenLevel { gameprofilerfiller.pop(); } @@ -13957,7 +14052,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 @Override public boolean shouldTickBlocksAt(long chunkPos) { // Paper start - replace player chunk loader system -@@ -797,11 +806,12 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -797,11 +836,12 @@ public class ServerLevel extends Level implements WorldGenLevel { protected void tickTime() { if (this.tickTime) { @@ -13974,7 +14069,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 this.setDayTime(this.levelData.getDayTime() + 1L); } -@@ -830,15 +840,23 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -830,15 +870,23 @@ public class ServerLevel extends Level implements WorldGenLevel { private void wakeUpAllPlayers() { this.sleepStatus.removeAllSleepers(); (this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList())).forEach((entityplayer) -> { // CraftBukkit - decompile error @@ -14001,7 +14096,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 ChunkPos chunkcoordintpair = chunk.getPos(); boolean flag = this.isRaining(); int j = chunkcoordintpair.getMinBlockX(); -@@ -846,7 +864,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -846,7 +894,7 @@ public class ServerLevel extends Level implements WorldGenLevel { ProfilerFiller gameprofilerfiller = this.getProfiler(); gameprofilerfiller.push("thunder"); @@ -14010,7 +14105,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - disable thunder blockposition.set(this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15))); // Paper -@@ -941,7 +959,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -941,7 +989,7 @@ public class ServerLevel extends Level implements WorldGenLevel { int yPos = (sectionIndex + minSection) << 4; for (int a = 0; a < randomTickSpeed; ++a) { int tickingBlocks = section.tickingList.size(); @@ -14019,7 +14114,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 if (index >= tickingBlocks) { continue; } -@@ -955,7 +973,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -955,7 +1003,7 @@ public class ServerLevel extends Level implements WorldGenLevel { BlockPos blockposition2 = blockposition.set(j + randomX, randomY, k + randomZ); BlockState iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw); @@ -14028,7 +14123,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 // We drop the fluid tick since LAVA is ALREADY TICKED by the above method (See LiquidBlock). // TODO CHECK ON UPDATE (ping the Canadian) } -@@ -1009,7 +1027,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1009,7 +1057,7 @@ public class ServerLevel extends Level implements WorldGenLevel { } public boolean isHandlingTick() { @@ -14037,7 +14132,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 } public boolean canSleepThroughNights() { -@@ -1041,6 +1059,14 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1041,6 +1089,14 @@ public class ServerLevel extends Level implements WorldGenLevel { } public void updateSleepingPlayerList() { @@ -14052,7 +14147,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 if (!this.players.isEmpty() && this.sleepStatus.update(this.players)) { this.announceSleepStatus(); } -@@ -1052,7 +1078,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1052,7 +1108,7 @@ public class ServerLevel extends Level implements WorldGenLevel { return this.server.getScoreboard(); } @@ -14061,7 +14156,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 boolean flag = this.isRaining(); if (this.dimensionType().hasSkyLight()) { -@@ -1138,23 +1164,24 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1138,23 +1194,24 @@ public class ServerLevel extends Level implements WorldGenLevel { this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.THUNDER_LEVEL_CHANGE, this.thunderLevel)); } // */ @@ -14095,7 +14190,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 } } // CraftBukkit end -@@ -1218,7 +1245,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1218,7 +1275,7 @@ public class ServerLevel extends Level implements WorldGenLevel { public void tickNonPassenger(Entity entity) { // Paper start - log detailed entity tick information @@ -14104,7 +14199,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 try { if (currentlyTickingEntity.get() == null) { currentlyTickingEntity.lazySet(entity); -@@ -1251,7 +1278,16 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1251,7 +1308,16 @@ public class ServerLevel extends Level implements WorldGenLevel { if (isActive) { // Paper - EAR 2 TimingHistory.activatedEntityTicks++; entity.tick(); @@ -14122,7 +14217,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 } else { entity.inactiveTick(); } // Paper - EAR 2 this.getProfiler().pop(); } finally { timer.stopTiming(); } // Paper - timings -@@ -1274,7 +1310,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1274,7 +1340,7 @@ public class ServerLevel extends Level implements WorldGenLevel { private void tickPassenger(Entity vehicle, Entity passenger) { if (!passenger.isRemoved() && passenger.getVehicle() == vehicle) { @@ -14131,7 +14226,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 // Paper - EAR 2 final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(passenger); co.aikar.timings.Timing timer = isActive ? passenger.getType().passengerTickTimer.startTiming() : passenger.getType().passengerInactiveTickTimer.startTiming(); // Paper -@@ -1291,7 +1327,16 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1291,7 +1357,16 @@ public class ServerLevel extends Level implements WorldGenLevel { // Paper start - EAR 2 if (isActive) { passenger.rideTick(); @@ -14149,7 +14244,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 } else { passenger.setDeltaMovement(Vec3.ZERO); passenger.inactiveTick(); -@@ -1379,7 +1424,15 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1379,7 +1454,15 @@ public class ServerLevel extends Level implements WorldGenLevel { // Paper - rewrite chunk system - entity saving moved into ChunkHolder } else if (close) { chunkproviderserver.close(false); } // Paper - rewrite chunk system @@ -14165,7 +14260,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 // CraftBukkit start - moved from MinecraftServer.saveChunks ServerLevel worldserver1 = this; -@@ -1387,12 +1440,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1387,12 +1470,7 @@ public class ServerLevel extends Level implements WorldGenLevel { this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save()); this.convertable.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData()); // CraftBukkit end @@ -14179,7 +14274,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 this.getChunkSource().getDataStorage().save(); } -@@ -1447,6 +1495,19 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1447,6 +1525,19 @@ public class ServerLevel extends Level implements WorldGenLevel { return list; } @@ -14199,7 +14294,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 @Nullable public ServerPlayer getRandomPlayer() { List list = this.getPlayers(LivingEntity::isAlive); -@@ -1548,8 +1609,8 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1548,8 +1639,8 @@ public class ServerLevel extends Level implements WorldGenLevel { } else { if (entity instanceof net.minecraft.world.entity.item.ItemEntity itemEntity && itemEntity.getItem().isEmpty()) return false; // Paper - Prevent empty items from being added // Paper start - capture all item additions to the world @@ -14210,7 +14305,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 return true; } // Paper end -@@ -1688,7 +1749,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1688,7 +1779,7 @@ public class ServerLevel extends Level implements WorldGenLevel { @Override public void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) { @@ -14219,7 +14314,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 String s = "recursive call to sendBlockUpdated"; Util.logAndPauseIfInIde("recursive call to sendBlockUpdated", new IllegalStateException("recursive call to sendBlockUpdated")); -@@ -1701,7 +1762,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1701,7 +1792,7 @@ public class ServerLevel extends Level implements WorldGenLevel { if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) { List list = new ObjectArrayList(); @@ -14228,7 +14323,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 while (iterator.hasNext()) { // CraftBukkit start - fix SPIGOT-6362 -@@ -1724,7 +1785,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1724,7 +1815,7 @@ public class ServerLevel extends Level implements WorldGenLevel { } try { @@ -14237,7 +14332,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 iterator = list.iterator(); while (iterator.hasNext()) { -@@ -1733,7 +1794,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1733,7 +1824,7 @@ public class ServerLevel extends Level implements WorldGenLevel { navigationabstract1.recomputePath(); } } finally { @@ -14246,7 +14341,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 } } -@@ -1742,23 +1803,23 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1742,23 +1833,23 @@ public class ServerLevel extends Level implements WorldGenLevel { @Override public void updateNeighborsAt(BlockPos pos, Block sourceBlock) { @@ -14275,7 +14370,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 } @Override -@@ -1784,7 +1845,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1784,7 +1875,7 @@ public class ServerLevel extends Level implements WorldGenLevel { explosion.clearToBlow(); } @@ -14284,7 +14379,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 while (iterator.hasNext()) { ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -@@ -1799,25 +1860,28 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1799,25 +1890,28 @@ public class ServerLevel extends Level implements WorldGenLevel { @Override public void blockEvent(BlockPos pos, Block block, int type, int data) { @@ -14319,7 +14414,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 } private boolean doBlockEvent(BlockEventData event) { -@@ -1828,12 +1892,12 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1828,12 +1922,12 @@ public class ServerLevel extends Level implements WorldGenLevel { @Override public LevelTicks getBlockTicks() { @@ -14334,7 +14429,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 } @Nonnull -@@ -1857,7 +1921,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1857,7 +1951,7 @@ public class ServerLevel extends Level implements WorldGenLevel { public int sendParticles(ServerPlayer sender, T t0, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6, boolean force) { // Paper start - Particle API Expansion @@ -14343,7 +14438,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 } public int sendParticles(List receivers, ServerPlayer sender, T t0, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6, boolean force) { // Paper end -@@ -1910,7 +1974,14 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1910,7 +2004,14 @@ public class ServerLevel extends Level implements WorldGenLevel { public Entity getEntityOrPart(int id) { Entity entity = (Entity) this.getEntities().get(id); @@ -14359,7 +14454,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 } @Nullable -@@ -1918,6 +1989,61 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1918,6 +2019,61 @@ public class ServerLevel extends Level implements WorldGenLevel { return (Entity) this.getEntities().get(uuid); } @@ -14421,7 +14516,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 @Nullable public BlockPos findNearestMapStructure(TagKey structureTag, BlockPos pos, int radius, boolean skipReferencedStructures) { if (!this.serverLevelData.worldGenOptions().generateStructures()) { // CraftBukkit -@@ -2082,7 +2208,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2082,7 +2238,7 @@ public class ServerLevel extends Level implements WorldGenLevel { if (forced) { flag1 = forcedchunk.getChunks().add(k); if (flag1) { @@ -14430,7 +14525,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 } } else { flag1 = forcedchunk.getChunks().remove(k); -@@ -2110,13 +2236,18 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2110,13 +2266,18 @@ public class ServerLevel extends Level implements WorldGenLevel { BlockPos blockposition1 = pos.immutable(); optional.ifPresent((holder) -> { @@ -14452,7 +14547,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 // Paper start if (optional.isEmpty() && this.getPoiManager().exists(blockposition1, poiType -> true)) { this.getPoiManager().remove(blockposition1); -@@ -2124,7 +2255,12 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2124,7 +2285,12 @@ public class ServerLevel extends Level implements WorldGenLevel { // Paper end this.getPoiManager().add(blockposition1, holder); DebugPackets.sendPoiAddedPacket(this, blockposition1); @@ -14466,7 +14561,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 }); } } -@@ -2171,7 +2307,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2171,7 +2337,7 @@ public class ServerLevel extends Level implements WorldGenLevel { BufferedWriter bufferedwriter = Files.newBufferedWriter(path.resolve("stats.txt")); try { @@ -14475,7 +14570,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 NaturalSpawner.SpawnState spawnercreature_d = this.getChunkSource().getLastSpawnState(); if (spawnercreature_d != null) { -@@ -2185,7 +2321,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2185,7 +2351,7 @@ public class ServerLevel extends Level implements WorldGenLevel { } bufferedwriter.write(String.format(Locale.ROOT, "entities: %s\n", this.entityLookup.getDebugInfo())); // Paper - rewrite chunk system @@ -14484,7 +14579,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 bufferedwriter.write(String.format(Locale.ROOT, "block_ticks: %d\n", this.getBlockTicks().count())); bufferedwriter.write(String.format(Locale.ROOT, "fluid_ticks: %d\n", this.getFluidTicks().count())); bufferedwriter.write("distance_manager: " + playerchunkmap.getDistanceManager().getDebugStatus() + "\n"); -@@ -2331,7 +2467,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2331,7 +2497,7 @@ public class ServerLevel extends Level implements WorldGenLevel { private void dumpBlockEntityTickers(Writer writer) throws IOException { CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("y").addColumn("z").addColumn("type").build(writer); @@ -14493,7 +14588,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 while (iterator.hasNext()) { TickingBlockEntity tickingblockentity = (TickingBlockEntity) iterator.next(); -@@ -2344,7 +2480,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2344,7 +2510,7 @@ public class ServerLevel extends Level implements WorldGenLevel { @VisibleForTesting public void clearBlockEvents(BoundingBox box) { @@ -14502,7 +14597,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 return box.isInside(blockactiondata.pos()); }); } -@@ -2353,7 +2489,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2353,7 +2519,7 @@ public class ServerLevel extends Level implements WorldGenLevel { public void blockUpdated(BlockPos pos, Block block) { if (!this.isDebug()) { // CraftBukkit start @@ -14511,7 +14606,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 return; } // CraftBukkit end -@@ -2396,9 +2532,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2396,9 +2562,7 @@ public class ServerLevel extends Level implements WorldGenLevel { @VisibleForTesting public String getWatchdogStats() { @@ -14522,7 +14617,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 } private static String getTypeCount(Iterable items, Function classifier) { -@@ -2431,6 +2565,12 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2431,6 +2595,12 @@ public class ServerLevel extends Level implements WorldGenLevel { public static void makeObsidianPlatform(ServerLevel worldserver, Entity entity) { // CraftBukkit end BlockPos blockposition = ServerLevel.END_SPAWN_POINT; @@ -14535,7 +14630,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 int i = blockposition.getX(); int j = blockposition.getY() - 2; int k = blockposition.getZ(); -@@ -2443,11 +2583,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2443,11 +2613,7 @@ public class ServerLevel extends Level implements WorldGenLevel { BlockPos.betweenClosed(i - 2, j, k - 2, i + 2, j, k + 2).forEach((blockposition1) -> { blockList.setBlock(blockposition1, Blocks.OBSIDIAN.defaultBlockState(), 3); }); @@ -14548,7 +14643,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 blockList.updateList(); } // CraftBukkit end -@@ -2468,13 +2604,14 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2468,13 +2634,14 @@ public class ServerLevel extends Level implements WorldGenLevel { } public void startTickingChunk(LevelChunk chunk) { @@ -14567,7 +14662,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 } @Override -@@ -2496,7 +2633,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2496,7 +2663,7 @@ public class ServerLevel extends Level implements WorldGenLevel { // Paper end - rewrite chunk system } @@ -14576,7 +14671,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 // Paper start - optimize is ticking ready type functions io.papermc.paper.chunk.system.scheduling.NewChunkHolder chunkHolder = this.chunkTaskScheduler.chunkHolderManager.getChunkHolder(chunkPos); // isTicking implies the chunk is loaded, and the chunk is loaded now implies the entities are loaded -@@ -2544,16 +2681,16 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2544,16 +2711,16 @@ public class ServerLevel extends Level implements WorldGenLevel { public void onCreated(Entity entity) {} public void onDestroyed(Entity entity) { @@ -14596,7 +14691,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 // Paper start - Reset pearls when they stop being ticked if (paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && entity instanceof net.minecraft.world.entity.projectile.ThrownEnderpearl pearl) { pearl.cachedOwner = null; -@@ -2581,7 +2718,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2581,7 +2748,7 @@ public class ServerLevel extends Level implements WorldGenLevel { Util.logAndPauseIfInIde("onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")); } @@ -14605,7 +14700,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 } if (entity instanceof EnderDragon) { -@@ -2592,7 +2729,9 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2592,7 +2759,9 @@ public class ServerLevel extends Level implements WorldGenLevel { for (int j = 0; j < i; ++j) { EnderDragonPart entitycomplexpart = aentitycomplexpart[j]; @@ -14615,7 +14710,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 } } -@@ -2666,7 +2805,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2666,7 +2835,7 @@ public class ServerLevel extends Level implements WorldGenLevel { Util.logAndPauseIfInIde("onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")); } @@ -14624,7 +14719,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 } if (entity instanceof EnderDragon) { -@@ -2677,13 +2816,16 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2677,13 +2846,16 @@ public class ServerLevel extends Level implements WorldGenLevel { for (int j = 0; j < i; ++j) { EnderDragonPart entitycomplexpart = aentitycomplexpart[j]; @@ -14642,7 +14737,7 @@ index 714637cdd9dcdbffa344b19e77944fb3c7541ff7..61fd4eea649fab254b3b2c0f160257e0 for (ServerPlayer player : ServerLevel.this.players) { player.getBukkitEntity().onEntityRemove(entity); diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 869daafbc236b3ff63f878e5fe28427fde75afe5..ecd5b4542f95717e830fe3d845e79090aa341c2b 100644 +index 869daafbc236b3ff63f878e5fe28427fde75afe5..ab060fe03c4c66a2bd0966679b503965849273fa 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -181,7 +181,7 @@ import org.bukkit.inventory.MainHand; @@ -14797,7 +14892,7 @@ index 869daafbc236b3ff63f878e5fe28427fde75afe5..ecd5b4542f95717e830fe3d845e79090 return horizontalSpawnArea <= 16 ? horizontalSpawnArea - 1 : 17; } -@@ -1147,6 +1189,338 @@ public class ServerPlayer extends Player { +@@ -1147,6 +1189,339 @@ public class ServerPlayer extends Player { } } @@ -14993,6 +15088,7 @@ index 869daafbc236b3ff63f878e5fe28427fde75afe5..ecd5b4542f95717e830fe3d845e79090 + this.getLevel().getCurrentWorldData().connections.remove(this.connection.connection); + this.getLevel().removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); + ++ this.spawnIn(destination); + this.transform(pos, yaw, pitch, speedDirectionUpdate); + + return this; @@ -15136,7 +15232,7 @@ index 869daafbc236b3ff63f878e5fe28427fde75afe5..ecd5b4542f95717e830fe3d845e79090 @Nullable @Override public Entity changeDimension(ServerLevel destination) { -@@ -2098,6 +2472,12 @@ public class ServerPlayer extends Player { +@@ -2098,6 +2473,12 @@ public class ServerPlayer extends Player { if (entity1 == entity) return; // new spec target is the current spec target @@ -15149,7 +15245,7 @@ index 869daafbc236b3ff63f878e5fe28427fde75afe5..ecd5b4542f95717e830fe3d845e79090 if (entity == this) { com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent playerStopSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity()); -@@ -2132,7 +2512,7 @@ public class ServerPlayer extends Player { +@@ -2132,7 +2513,7 @@ public class ServerPlayer extends Player { this.getBukkitEntity().teleport(new Location(entity.getCommandSenderWorld().getWorld(), entity.getX(), entity.getY(), entity.getZ(), this.getYRot(), this.getXRot()), TeleportCause.SPECTATE); // Correctly handle cross-world entities from api calls by using CB teleport // Make sure we're tracking the entity before sending @@ -15158,7 +15254,7 @@ index 869daafbc236b3ff63f878e5fe28427fde75afe5..ecd5b4542f95717e830fe3d845e79090 if (tracker != null) { // dumb plugins... tracker.updatePlayer(this); } -@@ -2567,7 +2947,7 @@ public class ServerPlayer extends Player { +@@ -2567,7 +2948,7 @@ public class ServerPlayer extends Player { this.experienceLevel = this.newLevel; this.totalExperience = this.newTotalExp; this.experienceProgress = 0; @@ -15292,10 +15388,10 @@ index 660693c6dc0ef86f4013df980b6d0c11c03e46cd..eef501b0558680e5563b0a15a93bd3ab LOGGER.error("Failed to remove ticket level for post chunk task " + new ChunkPos(chunkX, chunkZ), thr); } diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java -index 97d1ff2af23bac14e67bca5896843325aaa5bfc1..cf38de369a57e30a29dfa13e116f950b0dbf5904 100644 +index 97d1ff2af23bac14e67bca5896843325aaa5bfc1..77495a7bdde233c70a45e806446a59d6bde538af 100644 --- a/src/main/java/net/minecraft/server/level/TicketType.java +++ b/src/main/java/net/minecraft/server/level/TicketType.java -@@ -35,6 +35,12 @@ public class TicketType { +@@ -35,6 +35,14 @@ public class TicketType { public static final TicketType POI_LOAD = create("poi_load", Long::compareTo); public static final TicketType UNLOAD_COOLDOWN = create("unload_cooldown", (u1, u2) -> 0, 5 * 20); // Paper end - rewrite chunk system @@ -15304,6 +15400,8 @@ index 97d1ff2af23bac14e67bca5896843325aaa5bfc1..cf38de369a57e30a29dfa13e116f950b + public static final TicketType DELAYED = create("delay", (u1, u2) -> 0, 5); + public static final TicketType END_GATEWAY_EXIT_SEARCH = create("end_gateway_exit_search", Long::compareTo); + public static final TicketType NON_FULL_SYNC_LOAD = create("non_full_sync_load", Long::compareTo); ++ public static final TicketType NETHER_PORTAL_DOUBLE_CHECK = create("nether_portal_double_check", Long::compareTo); ++ public static final TicketType TELEPORT_HOLD_TICKET = create("teleport_hold_ticket", Long::compareTo); + // Folia end - region threading public static TicketType create(String name, Comparator argumentComparator) { @@ -15741,7 +15839,7 @@ index 3472f7f9b98d6d9c9f6465872803ef17fa67486d..e8e2d8e481ff798dc73bfdfe956cd7d9 private void updateSignText(ServerboundSignUpdatePacket packet, List signText) { diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index a25306fe8a35bb70a490e6a0c01d0340bbc0d781..805557d4fedd234a593ccf2655399a2b87ee6b60 100644 +index a25306fe8a35bb70a490e6a0c01d0340bbc0d781..626d99c785d2886bce605ba468ee24ce1710beb2 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -53,7 +53,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, @@ -15814,8 +15912,8 @@ index a25306fe8a35bb70a490e6a0c01d0340bbc0d781..805557d4fedd234a593ccf2655399a2b + // on the load callback for those chunks (so on the same region) + // this guarantees the chunk cannot unload under our feet + toComplete.addWaiter((org.bukkit.Location loc, Throwable t) -> { -+ int chunkX = net.minecraft.util.Mth.fastFloor(loc.getX()) >> 4; -+ int chunkZ = net.minecraft.util.Mth.fastFloor(loc.getZ()) >> 4; ++ int chunkX = net.minecraft.util.Mth.floor(loc.getX()) >> 4; ++ int chunkZ = net.minecraft.util.Mth.floor(loc.getZ()) >> 4; + + net.minecraft.server.level.ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld)loc.getWorld()).getHandle(); + // we just need to hold the chunks at loaded until the next tick @@ -16401,7 +16499,7 @@ index 6b5fd3e2e19c2d3d694df94f90fce0d310a1a86c..a7a48cf40db1e31ab03e0f42028b617b itemstack = entityliving1.getMainHandItem(); diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 1eaab1f6923e6aa34b643293347348e5cc19af3c..87e20dc4e787e7b875d97b6cdb6b3ce497f795e8 100644 +index 1eaab1f6923e6aa34b643293347348e5cc19af3c..b8fe79dff6b9917e3a053a52a9efff4679231501 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -165,7 +165,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { @@ -16584,7 +16682,7 @@ index 1eaab1f6923e6aa34b643293347348e5cc19af3c..87e20dc4e787e7b875d97b6cdb6b3ce4 return; } // CraftBukkit end -@@ -3361,6 +3387,662 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -3361,6 +3387,750 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { this.portalEntrancePos = original.portalEntrancePos; } @@ -16762,12 +16860,37 @@ index 1eaab1f6923e6aa34b643293347348e5cc19af3c..87e20dc4e787e7b875d97b6cdb6b3ce4 + protected final void placeInAsync(ServerLevel originWorld, ServerLevel destination, long teleportFlags, + EntityTreeNode passengerTree, java.util.function.Consumer teleportComplete) { + Vec3 pos = this.position(); ++ ChunkPos posChunk = new ChunkPos( ++ io.papermc.paper.util.CoordinateUtils.getChunkX(pos), ++ io.papermc.paper.util.CoordinateUtils.getChunkZ(pos) ++ ); ++ ++ // ensure the region is always ticking in case of a shutdown ++ // otherwise, the shutdown will not be able to complete the shutdown as it requires a ticking region ++ Long teleportHoldId = Long.valueOf(TELEPORT_HOLD_TICKET_GEN.getAndIncrement()); ++ originWorld.chunkSource.addTicketAtLevel( ++ TicketType.TELEPORT_HOLD_TICKET, posChunk, ++ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, ++ teleportHoldId ++ ); ++ final ServerLevel.PendingTeleport pendingTeleport = new ServerLevel.PendingTeleport(passengerTree, pos); ++ destination.pushPendingTeleport(pendingTeleport); + + Runnable scheduleEntityJoin = () -> { + io.papermc.paper.threadedregions.RegionisedServer.getInstance().taskQueue.queueTickTaskQueue( + destination, + io.papermc.paper.util.CoordinateUtils.getChunkX(pos), io.papermc.paper.util.CoordinateUtils.getChunkZ(pos), + () -> { ++ if (!destination.removePendingTeleport(pendingTeleport)) { ++ // shutdown logic placed the entity already, and we are shutting down - do nothing to ensure ++ // we do not produce any errors here ++ return; ++ } ++ originWorld.chunkSource.removeTicketAtLevel( ++ TicketType.TELEPORT_HOLD_TICKET, posChunk, ++ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, ++ teleportHoldId ++ ); + List fullTree = passengerTree.getFullTree(); + for (EntityTreeNode node : fullTree) { + node.root.placeSingleSync(originWorld, destination, node, teleportFlags); @@ -17025,6 +17148,9 @@ index 1eaab1f6923e6aa34b643293347348e5cc19af3c..87e20dc4e787e7b875d97b6cdb6b3ce4 + return this.portalToAsync(destination, false, PortalType.NETHER, null); + } + ++ private static final java.util.concurrent.atomic.AtomicLong CREATE_PORTAL_DOUBLE_CHECK = new java.util.concurrent.atomic.AtomicLong(); ++ private static final java.util.concurrent.atomic.AtomicLong TELEPORT_HOLD_TICKET_GEN = new java.util.concurrent.atomic.AtomicLong(); ++ + // To simplify portal logic, in region threading both players + // and non-player entities will create portals. By guaranteeing + // that the teleportation can take place, we can simply @@ -17142,11 +17268,43 @@ index 1eaab1f6923e6aa34b643293347348e5cc19af3c..87e20dc4e787e7b875d97b6cdb6b3ce4 + return; + } + ++ // add tickets so that we can re-search for a portal once the chunks are loaded ++ Long ticketId = Long.valueOf(CREATE_PORTAL_DOUBLE_CHECK.getAndIncrement()); ++ for (net.minecraft.world.level.chunk.ChunkAccess chunk : chunks) { ++ destination.chunkSource.addTicketAtLevel( ++ TicketType.NETHER_PORTAL_DOUBLE_CHECK, chunk.getPos(), ++ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager.FULL_LOADED_TICKET_LEVEL, ++ ticketId ++ ); ++ } ++ + // no portal found - create one + destination.loadChunksAsync( + targetPos, portalCreateRadius + 32, + ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGH, + (chunks2) -> { ++ // don't need the tickets anymore ++ // note: we expect removeTicketsAtLevel to add an unknown ticket for us automatically ++ // if the ticket level were to decrease ++ for (net.minecraft.world.level.chunk.ChunkAccess chunk : chunks) { ++ destination.chunkSource.removeTicketAtLevel( ++ TicketType.NETHER_PORTAL_DOUBLE_CHECK, chunk.getPos(), ++ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager.FULL_LOADED_TICKET_LEVEL, ++ ticketId ++ ); ++ } ++ ++ // when two entities portal at the same time, it is possible that both entities reach this ++ // part of the code - and create a double portal ++ // to fix this, we just issue another search to try and see if another entity created ++ // a portal nearby ++ BlockUtil.FoundRectangle existingTryAgain = ++ destination.getPortalForcer().findPortalAround(targetPos, destinationBorder, portalSearchRadius).orElse(null); ++ if (existingTryAgain != null) { ++ portalFound.complete(existingTryAgain); ++ return; ++ } ++ + // we do not have the correct entity reference here + BlockUtil.FoundRectangle createdPortal = + destination.getPortalForcer().createPortal(targetPos, originalPortalDirection, null, portalCreateRadius).orElse(null); @@ -17204,6 +17362,12 @@ index 1eaab1f6923e6aa34b643293347348e5cc19af3c..87e20dc4e787e7b875d97b6cdb6b3ce4 + return false; + } + ++ Vec3 initialPosition = this.position(); ++ ChunkPos initialPositionChunk = new ChunkPos( ++ io.papermc.paper.util.CoordinateUtils.getChunkX(initialPosition), ++ io.papermc.paper.util.CoordinateUtils.getChunkZ(initialPosition) ++ ); ++ + // first, remove entity/passengers from world + EntityTreeNode passengerTree = this.detachPassengers(); + List fullPassengerTree = passengerTree.getFullTree(); @@ -17221,10 +17385,32 @@ index 1eaab1f6923e6aa34b643293347348e5cc19af3c..87e20dc4e787e7b875d97b6cdb6b3ce4 + node.root.setPortalCooldown(); + } + ++ // ensure the region is always ticking in case of a shutdown ++ // otherwise, the shutdown will not be able to complete the shutdown as it requires a ticking region ++ Long teleportHoldId = Long.valueOf(TELEPORT_HOLD_TICKET_GEN.getAndIncrement()); ++ originWorld.chunkSource.addTicketAtLevel( ++ TicketType.TELEPORT_HOLD_TICKET, initialPositionChunk, ++ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, ++ teleportHoldId ++ ); ++ ++ ServerLevel.PendingTeleport beforeFindDestination = new ServerLevel.PendingTeleport(passengerTree, initialPosition); ++ originWorld.pushPendingTeleport(beforeFindDestination); ++ + ca.spottedleaf.concurrentutil.completable.Completable portalInfoCompletable + = new ca.spottedleaf.concurrentutil.completable.Completable<>(); + + portalInfoCompletable.addWaiter((PortalInfo info, Throwable throwable) -> { ++ if (!originWorld.removePendingTeleport(beforeFindDestination)) { ++ // the shutdown thread has placed us back into the origin world at the original position ++ // we just have to abandon this teleport to prevent duplication ++ return; ++ } ++ originWorld.chunkSource.removeTicketAtLevel( ++ TicketType.TELEPORT_HOLD_TICKET, initialPositionChunk, ++ io.papermc.paper.chunk.system.scheduling.ChunkHolderManager.MAX_TICKET_LEVEL, ++ teleportHoldId ++ ); + // adjust passenger tree to final pos + for (EntityTreeNode node : fullPassengerTree) { + node.root.transform(info.pos, Float.valueOf(info.yRot), Float.valueOf(info.xRot), info.speed); @@ -17247,7 +17433,7 @@ index 1eaab1f6923e6aa34b643293347348e5cc19af3c..87e20dc4e787e7b875d97b6cdb6b3ce4 @Nullable public Entity changeDimension(ServerLevel destination) { // CraftBukkit start -@@ -3859,17 +4541,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -3859,17 +4629,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { // Paper start public void startSeenByPlayer(ServerPlayer player) { @@ -17267,7 +17453,7 @@ index 1eaab1f6923e6aa34b643293347348e5cc19af3c..87e20dc4e787e7b875d97b6cdb6b3ce4 } // Paper end -@@ -4341,7 +5019,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -4341,7 +5107,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } } // Paper end - fix MC-4 @@ -17277,7 +17463,7 @@ index 1eaab1f6923e6aa34b643293347348e5cc19af3c..87e20dc4e787e7b875d97b6cdb6b3ce4 synchronized (this.posLock) { // Paper this.position = new Vec3(x, y, z); } // Paper -@@ -4362,7 +5041,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -4362,7 +5129,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { // Paper start - never allow AABB to become desynced from position // hanging has its own special logic @@ -17286,7 +17472,7 @@ index 1eaab1f6923e6aa34b643293347348e5cc19af3c..87e20dc4e787e7b875d97b6cdb6b3ce4 this.setBoundingBox(this.makeBoundingBox()); } // Paper end -@@ -4461,7 +5140,23 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -4461,7 +5228,23 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { if (reason != RemovalReason.UNLOADED_TO_CHUNK) this.getPassengers().forEach(Entity::stopRiding); // Paper - chunk system - don't adjust passenger state when unloading, it's just not safe (and messes with our logic in entity chunk unload) this.levelCallback.onRemove(reason); diff --git a/patches/server/0005-Increase-parallelism-for-neighbour-writing-chunk-sta.patch b/patches/server/0005-Increase-parallelism-for-neighbour-writing-chunk-sta.patch index 1d38f51..298bbc4 100644 --- a/patches/server/0005-Increase-parallelism-for-neighbour-writing-chunk-sta.patch +++ b/patches/server/0005-Increase-parallelism-for-neighbour-writing-chunk-sta.patch @@ -10,7 +10,7 @@ will allow the chunk system to scale beyond 10 threads per world. diff --git a/src/main/java/io/papermc/paper/chunk/system/RegionisedPlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/system/RegionisedPlayerChunkLoader.java -index 5cccfcf45b3c3cdfdebdf47dc674934441cc0c4c..ede3fbe3287a08b9b1a026f8443f1c97d4205dde 100644 +index 245242b276e3de1edde1e2ebd0ce518fd0d08117..acf77a7745db2e28bd674107cdcb65d278625445 100644 --- a/src/main/java/io/papermc/paper/chunk/system/RegionisedPlayerChunkLoader.java +++ b/src/main/java/io/papermc/paper/chunk/system/RegionisedPlayerChunkLoader.java @@ -12,6 +12,7 @@ import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; @@ -32,7 +32,7 @@ index 5cccfcf45b3c3cdfdebdf47dc674934441cc0c4c..ede3fbe3287a08b9b1a026f8443f1c97 import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -@@ -284,7 +286,92 @@ public class RegionisedPlayerChunkLoader { +@@ -286,7 +288,92 @@ public class RegionisedPlayerChunkLoader { } } @@ -196,10 +196,10 @@ index 0b7a2b0ead4f3bc07bfd9a38c2b7cf024bd140c6..36e93fefdfbebddce4c153974c7cd81a final int chunkX = CoordinateUtils.getChunkX(coordinate); final int chunkZ = CoordinateUtils.getChunkZ(coordinate); diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java -index 309b45885edc1400ae5a97cac7e5e5a19d73be0c..a5d44db5e1cc26d871c9db7727fdbae571880663 100644 +index 32b88d7902e877e1cce0b7635cbfa67b84b8eac0..89e8b5d3a62241df0e3cb5c296f1deb754305843 100644 --- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java +++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java -@@ -1310,17 +1310,23 @@ public final class ChunkHolderManager { +@@ -1306,17 +1306,23 @@ public final class ChunkHolderManager { } public Boolean tryDrainTicketUpdates() { diff --git a/regiontodo.txt b/regiontodo.txt index 61e421f..25908f7 100644 --- a/regiontodo.txt +++ b/regiontodo.txt @@ -1,5 +1,4 @@ Get done before testing: -- make sure async teleport / player join / async place entities are saved on shutdown - make scheduler load chunks better Pre-Test: List of things not fully tested