From 1b0a5071d06c2d380cd63492937ba4a8b1030c16 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Thu, 8 Jun 2023 20:15:55 -0700 Subject: [PATCH] Initial patch apply --- build.gradle.kts | 2 +- gradle.properties | 6 +- gradlew.bat | 0 install.bat | 0 jar.bat | 0 patch.bat | 0 patches/server/0001-Build-changes.patch | 8 +- patches/server/0002-MC-Dev-fixes.patch | 2 +- .../0003-New-player-chunk-loader-system.patch | 2350 -------- ...ions.patch => 0003-Threaded-Regions.patch} | 3127 ++++------ ....EMPTY-not-rely-on-the-main-thread-f.patch | 395 -- ...ns.patch => 0004-Max-pending-logins.patch} | 2 +- ...k-system-throughput-counters-to-tps.patch} | 0 ...lism-for-neighbour-writing-chunk-sta.patch | 1009 ---- ...getHandle-and-overrides-perform-thr.patch} | 50 +- ...he-whether-region-files-do-not-exist.patch | 119 - ...007-Disable-mid-tick-task-execution.patch} | 4 +- ...dOperationException-for-broken-APIs.patch} | 2 +- ... => 0009-Fix-tests-by-removing-them.patch} | 0 ...-github.com-PaperMC-paperweight-iss.patch} | 2 +- ...o-be-explicitly-marked-as-Folia-sup.patch} | 0 ... 0012-Lag-compensate-block-breaking.patch} | 4 +- ...ates-in-non-loaded-or-non-owned-chu.patch} | 36 +- ...world-tile-entities-on-worldgen-thr.patch} | 4 +- ...ccess-when-waking-players-up-during.patch} | 8 +- ...ng-lock-in-NewChunkHolder-onFullChun.patch | 39 - ...cess-POI-data-for-lodestone-compass.patch} | 0 ...ased-locking-to-increase-chunk-syste.patch | 482 ++ ...-Synchronize-PaperPermissionManager.patch} | 0 ... => 0019-Fix-off-region-raid-heroes.patch} | 2 +- ...ased-locking-to-increase-chunk-syste.patch | 5040 ----------------- ...load-tasks-as-completed-before-relea.patch | 110 - ...chunk-load-tasks-that-were-not-sched.patch | 63 - ...te-light-list-on-protochunk-deserial.patch | 68 - rb.bat | 0 35 files changed, 1660 insertions(+), 11274 deletions(-) mode change 100644 => 100755 gradlew.bat mode change 100644 => 100755 install.bat mode change 100644 => 100755 jar.bat mode change 100644 => 100755 patch.bat delete mode 100644 patches/server/0003-New-player-chunk-loader-system.patch rename patches/server/{0005-Threaded-Regions.patch => 0003-Threaded-Regions.patch} (90%) delete mode 100644 patches/server/0004-Make-ChunkStatus.EMPTY-not-rely-on-the-main-thread-f.patch rename patches/server/{0008-Max-pending-logins.patch => 0004-Max-pending-logins.patch} (96%) rename patches/server/{0009-Add-chunk-system-throughput-counters-to-tps.patch => 0005-Add-chunk-system-throughput-counters-to-tps.patch} (100%) delete mode 100644 patches/server/0006-Increase-parallelism-for-neighbour-writing-chunk-sta.patch rename patches/server/{0010-Make-CraftEntity-getHandle-and-overrides-perform-thr.patch => 0006-Make-CraftEntity-getHandle-and-overrides-perform-thr.patch} (98%) delete mode 100644 patches/server/0007-Cache-whether-region-files-do-not-exist.patch rename patches/server/{0011-Disable-mid-tick-task-execution.patch => 0007-Disable-mid-tick-task-execution.patch} (88%) rename patches/server/{0012-Throw-UnsupportedOperationException-for-broken-APIs.patch => 0008-Throw-UnsupportedOperationException-for-broken-APIs.patch} (98%) rename patches/server/{0013-Fix-tests-by-removing-them.patch => 0009-Fix-tests-by-removing-them.patch} (100%) rename patches/server/{0014-Work-around-https-github.com-PaperMC-paperweight-iss.patch => 0010-Work-around-https-github.com-PaperMC-paperweight-iss.patch} (92%) rename patches/server/{0015-Require-plugins-to-be-explicitly-marked-as-Folia-sup.patch => 0011-Require-plugins-to-be-explicitly-marked-as-Folia-sup.patch} (100%) rename patches/server/{0017-Lag-compensate-block-breaking.patch => 0012-Lag-compensate-block-breaking.patch} (94%) rename patches/server/{0018-Prevent-block-updates-in-non-loaded-or-non-owned-chu.patch => 0013-Prevent-block-updates-in-non-loaded-or-non-owned-chu.patch} (78%) rename patches/server/{0019-Block-reading-in-world-tile-entities-on-worldgen-thr.patch => 0014-Block-reading-in-world-tile-entities-on-worldgen-thr.patch} (87%) rename patches/server/{0020-Skip-worldstate-access-when-waking-players-up-during.patch => 0015-Skip-worldstate-access-when-waking-players-up-during.patch} (83%) delete mode 100644 patches/server/0016-Acquire-scheduling-lock-in-NewChunkHolder-onFullChun.patch rename patches/server/{0021-Do-not-access-POI-data-for-lodestone-compass.patch => 0016-Do-not-access-POI-data-for-lodestone-compass.patch} (100%) create mode 100644 patches/server/0017-Use-coordinate-based-locking-to-increase-chunk-syste.patch rename patches/server/{0023-Synchronize-PaperPermissionManager.patch => 0018-Synchronize-PaperPermissionManager.patch} (100%) rename patches/server/{0027-Fix-off-region-raid-heroes.patch => 0019-Fix-off-region-raid-heroes.patch} (96%) delete mode 100644 patches/server/0022-Use-coordinate-based-locking-to-increase-chunk-syste.patch delete mode 100644 patches/server/0024-Mark-POI-Entity-load-tasks-as-completed-before-relea.patch delete mode 100644 patches/server/0025-Properly-cancel-chunk-load-tasks-that-were-not-sched.patch delete mode 100644 patches/server/0026-Always-recalculate-light-list-on-protochunk-deserial.patch mode change 100644 => 100755 rb.bat diff --git a/build.gradle.kts b/build.gradle.kts index a5208ee..9e49b28 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ import io.papermc.paperweight.tasks.RebuildGitPatches plugins { java `maven-publish` - id("com.github.johnrengelman.shadow") version "8.1.0" apply false + id("com.github.johnrengelman.shadow") version "8.1.1" apply false id("io.papermc.paperweight.patcher") version "1.5.5" } diff --git a/gradle.properties b/gradle.properties index 9c5716e..a4b3a7a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,8 @@ group=dev.folia -version=1.19.4-R0.1-SNAPSHOT -mcVersion=1.19.4 -paperRef=bc4a6647c99ae98c52c1c81597834be8fec6aa0d +version=1.20-R0.1-SNAPSHOT +mcVersion=1.20 +paperRef=04509f0234dfd4c05be002de1985ab4915c2092c org.gradle.caching=true org.gradle.parallel=true diff --git a/gradlew.bat b/gradlew.bat old mode 100644 new mode 100755 diff --git a/install.bat b/install.bat old mode 100644 new mode 100755 diff --git a/jar.bat b/jar.bat old mode 100644 new mode 100755 diff --git a/patch.bat b/patch.bat old mode 100644 new mode 100755 diff --git a/patches/server/0001-Build-changes.patch b/patches/server/0001-Build-changes.patch index 5e734a7..84487e9 100644 --- a/patches/server/0001-Build-changes.patch +++ b/patches/server/0001-Build-changes.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Build changes diff --git a/build.gradle.kts b/build.gradle.kts -index 4f2fa65ade89c5703451dad4f80eeef162b277d1..ec4ce20c9f3dfb488cbd0874b79cb612a12def6a 100644 +index a3a76b9b7efa773117d2ee1ce53ef784b09b277d..85f5bedaf294dc48cab9cd5380ed4edeb4166a40 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,8 +7,12 @@ plugins { @@ -88,10 +88,10 @@ index 9d687da5bdf398bb3f6c84cdf1249a7213d09f2e..e2f704c115fd6e00960bb56bb0779f11 ).openBufferedStream()) { JsonObject json = new Gson().fromJson(reader, JsonObject.class); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 9f15d9dbdfa74a0640b1a2b4ff695609d4758a4c..3219482b96cab8262e393a790c88d903d7de5166 100644 +index fb82bb52f219e7683fe1d3c0fb3acbe2251de8d4..cdb9925e8c4771831a7fe8bbcb705278c51aa0d2 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1682,7 +1682,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { diff --git a/patches/server/0003-New-player-chunk-loader-system.patch b/patches/server/0003-New-player-chunk-loader-system.patch deleted file mode 100644 index 13d651a..0000000 --- a/patches/server/0003-New-player-chunk-loader-system.patch +++ /dev/null @@ -1,2350 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Wed, 1 Feb 2023 21:06:31 -0800 -Subject: [PATCH] New player chunk loader system - - -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java -index 26fa2caa18a9194e57574a4a7fa9f7a4265740e0..5aa1c61a8a82c91514d35df62db7f4bd67aa27a1 100644 ---- a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java -+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java -@@ -71,6 +71,10 @@ public final class PrioritisedThreadPool { - return Arrays.copyOf(this.threads, this.threads.length, Thread[].class); - } - -+ public int getThreadCount() { -+ return this.threads.length; -+ } -+ - public PrioritisedPoolExecutor createExecutor(final String name, final int parallelism) { - synchronized (this.nonShutdownQueues) { - if (this.shutdown) { -diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java -index c07eb451a576811a39021f6f97103c77488fd001..a2f71a6d1a9e98133dff6cd0f625da9435a8af14 100644 ---- a/src/main/java/co/aikar/timings/TimingsExport.java -+++ b/src/main/java/co/aikar/timings/TimingsExport.java -@@ -164,9 +164,9 @@ public class TimingsExport extends Thread { - return pair(rule, world.getWorld().getGameRuleValue(rule)); - })), - // Paper start - replace chunk loader system -- pair("ticking-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance()), -- pair("no-ticking-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance()), -- pair("sending-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance()) -+ pair("ticking-distance", world.getWorld().getSimulationDistance()), -+ pair("no-ticking-distance", world.getWorld().getViewDistance()), -+ pair("sending-distance", world.getWorld().getSendViewDistance()) - // Paper end - replace chunk loader system - )); - })); -diff --git a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java -index 0e45a340ae534caf676b7f9d0adcbcee5829925e..61d03808c8d1ab822d9b2f31fab0de14089a3b15 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java -+++ b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java -@@ -129,15 +129,15 @@ public final class ChunkSystem { - } - - public static int getSendViewDistance(final ServerPlayer player) { -- return io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player); -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPISendViewDistance(player); - } - - public static int getLoadViewDistance(final ServerPlayer player) { -- return io.papermc.paper.chunk.PlayerChunkLoader.getLoadViewDistance(player); -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getLoadViewDistance(player); - } - - public static int getTickViewDistance(final ServerPlayer player) { -- return io.papermc.paper.chunk.PlayerChunkLoader.getTickViewDistance(player); -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPITickViewDistance(player); - } - - private ChunkSystem() { -diff --git a/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java -new file mode 100644 -index 0000000000000000000000000000000000000000..63c69c4da5fcbd5901c9fc3427f69626e16492ee ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java -@@ -0,0 +1,1332 @@ -+package io.papermc.paper.chunk.system; -+ -+import ca.spottedleaf.concurrentutil.collection.SRSWLinkedQueue; -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import io.papermc.paper.chunk.system.io.RegionFileIOThread; -+import io.papermc.paper.chunk.system.scheduling.ChunkHolderManager; -+import io.papermc.paper.configuration.GlobalConfiguration; -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.IntegerUtil; -+import io.papermc.paper.util.TickThread; -+import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import it.unimi.dsi.fastutil.longs.LongComparator; -+import it.unimi.dsi.fastutil.longs.LongHeapPriorityQueue; -+import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -+import net.minecraft.network.protocol.Packet; -+import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket; -+import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket; -+import net.minecraft.network.protocol.game.ClientboundSetSimulationDistancePacket; -+import net.minecraft.server.level.ChunkMap; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.level.TicketType; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.GameRules; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.LevelChunk; -+import net.minecraft.world.level.levelgen.BelowZeroRetrogen; -+import org.apache.commons.lang3.mutable.MutableObject; -+import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.entity.Player; -+import java.lang.invoke.VarHandle; -+import java.util.ArrayDeque; -+import java.util.concurrent.TimeUnit; -+import java.util.concurrent.atomic.AtomicLong; -+ -+public class RegionizedPlayerChunkLoader { -+ -+ public static final TicketType REGION_PLAYER_TICKET = TicketType.create("region_player_ticket", Long::compareTo); -+ -+ public static final int MIN_VIEW_DISTANCE = 2; -+ public static final int MAX_VIEW_DISTANCE = 32; -+ -+ public static final int TICK_TICKET_LEVEL = 31; -+ public static final int GENERATED_TICKET_LEVEL = 33 + ChunkStatus.getDistance(ChunkStatus.FULL); -+ public static final int LOADED_TICKET_LEVEL = 33 + ChunkStatus.getDistance(ChunkStatus.EMPTY); -+ -+ public static final record ViewDistances( -+ int tickViewDistance, -+ int loadViewDistance, -+ int sendViewDistance -+ ) { -+ public ViewDistances setTickViewDistance(final int distance) { -+ return new ViewDistances(distance, this.loadViewDistance, this.sendViewDistance); -+ } -+ -+ public ViewDistances setLoadViewDistance(final int distance) { -+ return new ViewDistances(this.tickViewDistance, distance, this.sendViewDistance); -+ } -+ -+ -+ public ViewDistances setSendViewDistance(final int distance) { -+ return new ViewDistances(this.tickViewDistance, this.loadViewDistance, distance); -+ } -+ } -+ -+ public static int getAPITickViewDistance(final Player player) { -+ return getAPITickViewDistance(((CraftPlayer)player).getHandle()); -+ } -+ -+ public static int getAPITickViewDistance(final ServerPlayer player) { -+ final ServerLevel level = (ServerLevel)player.level; -+ final PlayerChunkLoaderData data = player.chunkLoader; -+ if (data == null) { -+ return level.playerChunkLoader.getAPITickDistance(); -+ } -+ return data.lastTickDistance; -+ } -+ -+ public static int getAPIViewDistance(final Player player) { -+ return getAPIViewDistance(((CraftPlayer)player).getHandle()); -+ } -+ -+ public static int getAPIViewDistance(final ServerPlayer player) { -+ final ServerLevel level = (ServerLevel)player.level; -+ final PlayerChunkLoaderData data = player.chunkLoader; -+ if (data == null) { -+ return level.playerChunkLoader.getAPIViewDistance(); -+ } -+ // view distance = load distance + 1 -+ return data.lastLoadDistance - 1; -+ } -+ -+ public static int getLoadViewDistance(final ServerPlayer player) { -+ final ServerLevel level = (ServerLevel)player.level; -+ final PlayerChunkLoaderData data = player.chunkLoader; -+ if (data == null) { -+ return level.playerChunkLoader.getAPIViewDistance(); -+ } -+ // view distance = load distance + 1 -+ return data.lastLoadDistance - 1; -+ } -+ -+ public static int getAPISendViewDistance(final Player player) { -+ return getAPISendViewDistance(((CraftPlayer)player).getHandle()); -+ } -+ -+ public static int getAPISendViewDistance(final ServerPlayer player) { -+ final ServerLevel level = (ServerLevel)player.level; -+ final PlayerChunkLoaderData data = player.chunkLoader; -+ if (data == null) { -+ return level.playerChunkLoader.getAPISendViewDistance(); -+ } -+ return data.lastSendDistance; -+ } -+ -+ private final ServerLevel world; -+ -+ public RegionizedPlayerChunkLoader(final ServerLevel world) { -+ this.world = world; -+ } -+ -+ public void addPlayer(final ServerPlayer player) { -+ TickThread.ensureTickThread(player, "Cannot add player to player chunk loader async"); -+ if (!player.isRealPlayer) { -+ return; -+ } -+ -+ if (player.chunkLoader != null) { -+ throw new IllegalStateException("Player is already added to player chunk loader"); -+ } -+ -+ final PlayerChunkLoaderData loader = new PlayerChunkLoaderData(this.world, player); -+ -+ player.chunkLoader = loader; -+ loader.add(); -+ } -+ -+ public void updatePlayer(final ServerPlayer player) { -+ final PlayerChunkLoaderData loader = player.chunkLoader; -+ if (loader != null) { -+ loader.update(); -+ } -+ } -+ -+ public void removePlayer(final ServerPlayer player) { -+ TickThread.ensureTickThread(player, "Cannot remove player from player chunk loader async"); -+ if (!player.isRealPlayer) { -+ return; -+ } -+ -+ final PlayerChunkLoaderData loader = player.chunkLoader; -+ -+ if (loader == null) { -+ throw new IllegalStateException("Player is already removed from player chunk loader"); -+ } -+ -+ loader.remove(); -+ player.chunkLoader = null; -+ } -+ -+ public void setSendDistance(final int distance) { -+ this.world.setSendViewDistance(distance); -+ } -+ -+ public void setLoadDistance(final int distance) { -+ this.world.setLoadViewDistance(distance); -+ } -+ -+ public void setTickDistance(final int distance) { -+ this.world.setTickViewDistance(distance); -+ } -+ -+ // Note: follow the player chunk loader so everything stays consistent... -+ public int getAPITickDistance() { -+ final ViewDistances distances = this.world.getViewDistances(); -+ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance); -+ return tickViewDistance; -+ } -+ -+ public int getAPIViewDistance() { -+ final ViewDistances distances = this.world.getViewDistances(); -+ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance); -+ final int loadDistance = PlayerChunkLoaderData.getLoadViewDistance(tickViewDistance, -1, distances.loadViewDistance); -+ -+ // loadDistance = api view distance + 1 -+ return loadDistance - 1; -+ } -+ -+ public int getAPISendViewDistance() { -+ final ViewDistances distances = this.world.getViewDistances(); -+ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance); -+ final int loadDistance = PlayerChunkLoaderData.getLoadViewDistance(tickViewDistance, -1, distances.loadViewDistance); -+ final int sendViewDistance = PlayerChunkLoaderData.getSendViewDistance( -+ loadDistance, -1, -1, distances.sendViewDistance -+ ); -+ -+ return sendViewDistance; -+ } -+ -+ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ, final boolean borderOnly) { -+ return borderOnly ? this.isChunkSentBorderOnly(player, chunkX, chunkZ) : this.isChunkSent(player, chunkX, chunkZ); -+ } -+ -+ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ) { -+ final PlayerChunkLoaderData loader = player.chunkLoader; -+ if (loader == null) { -+ return false; -+ } -+ -+ return loader.sentChunks.contains(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ -+ public boolean isChunkSentBorderOnly(final ServerPlayer player, final int chunkX, final int chunkZ) { -+ final PlayerChunkLoaderData loader = player.chunkLoader; -+ if (loader == null) { -+ return false; -+ } -+ -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ if (!loader.sentChunks.contains(CoordinateUtils.getChunkKey(dx + chunkX, dz + chunkZ))) { -+ return true; -+ } -+ } -+ } -+ -+ return false; -+ } -+ -+ public void tick() { -+ TickThread.ensureTickThread("Cannot tick player chunk loader async"); -+ long currTime = System.nanoTime(); -+ for (final ServerPlayer player : new java.util.ArrayList<>(this.world.players())) { -+ final PlayerChunkLoaderData loader = player.chunkLoader; -+ if (loader == null || loader.world != this.world) { -+ // not our problem anymore -+ continue; -+ } -+ loader.update(); // can't invoke plugin logic -+ loader.updateQueues(currTime); -+ } -+ } -+ -+ private static long[] generateBFSOrder(final int radius) { -+ final LongArrayList chunks = new LongArrayList(); -+ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue(); -+ final LongOpenHashSet seen = new LongOpenHashSet(); -+ -+ seen.add(CoordinateUtils.getChunkKey(0, 0)); -+ queue.enqueue(CoordinateUtils.getChunkKey(0, 0)); -+ while (!queue.isEmpty()) { -+ final long chunk = queue.dequeueLong(); -+ final int chunkX = CoordinateUtils.getChunkX(chunk); -+ final int chunkZ = CoordinateUtils.getChunkZ(chunk); -+ -+ // important that the addition to the list is here, rather than when enqueueing neighbours -+ // ensures the order is actually kept -+ chunks.add(chunk); -+ -+ // -x -+ final long n1 = CoordinateUtils.getChunkKey(chunkX - 1, chunkZ); -+ // -z -+ final long n2 = CoordinateUtils.getChunkKey(chunkX, chunkZ - 1); -+ // +x -+ final long n3 = CoordinateUtils.getChunkKey(chunkX + 1, chunkZ); -+ // +z -+ final long n4 = CoordinateUtils.getChunkKey(chunkX, chunkZ + 1); -+ -+ final long[] list = new long[] {n1, n2, n3, n4}; -+ -+ for (final long neighbour : list) { -+ final int neighbourX = CoordinateUtils.getChunkX(neighbour); -+ final int neighbourZ = CoordinateUtils.getChunkZ(neighbour); -+ if (Math.max(Math.abs(neighbourX), Math.abs(neighbourZ)) > radius) { -+ // don't enqueue out of range -+ continue; -+ } -+ if (!seen.add(neighbour)) { -+ continue; -+ } -+ queue.enqueue(neighbour); -+ } -+ } -+ -+ return chunks.toLongArray(); -+ } -+ -+ public static final class PlayerChunkLoaderData { -+ -+ private static final AtomicLong ID_GENERATOR = new AtomicLong(); -+ private final long id = ID_GENERATOR.incrementAndGet(); -+ private final Long idBoxed = Long.valueOf(this.id); -+ -+ // expected that this list returns for a given radius, the set of chunks ordered -+ // by manhattan distance -+ private static final long[][] SEARCH_RADIUS_ITERATION_LIST = new long[65][]; -+ static { -+ for (int i = 0; i < SEARCH_RADIUS_ITERATION_LIST.length; ++i) { -+ // a BFS around -x, -z, +x, +z will give increasing manhatten distance -+ SEARCH_RADIUS_ITERATION_LIST[i] = generateBFSOrder(i); -+ } -+ } -+ -+ private static final long MAX_RATE = 10_000L; -+ -+ private final ServerPlayer player; -+ private final ServerLevel world; -+ -+ private int lastChunkX = Integer.MIN_VALUE; -+ private int lastChunkZ = Integer.MIN_VALUE; -+ -+ private int lastSendDistance = Integer.MIN_VALUE; -+ private int lastLoadDistance = Integer.MIN_VALUE; -+ private int lastTickDistance = Integer.MIN_VALUE; -+ -+ private int lastSentChunkCenterX = Integer.MIN_VALUE; -+ private int lastSentChunkCenterZ = Integer.MIN_VALUE; -+ -+ private int lastSentChunkRadius = Integer.MIN_VALUE; -+ private int lastSentSimulationDistance = Integer.MIN_VALUE; -+ -+ private boolean canGenerateChunks = true; -+ -+ private final ArrayDeque> delayedTicketOps = new ArrayDeque<>(); -+ private final LongOpenHashSet sentChunks = new LongOpenHashSet(); -+ -+ private static final byte CHUNK_TICKET_STAGE_NONE = 0; -+ private static final byte CHUNK_TICKET_STAGE_LOADING = 1; -+ private static final byte CHUNK_TICKET_STAGE_LOADED = 2; -+ private static final byte CHUNK_TICKET_STAGE_GENERATING = 3; -+ private static final byte CHUNK_TICKET_STAGE_GENERATED = 4; -+ private static final byte CHUNK_TICKET_STAGE_TICK = 5; -+ private static final int[] TICKET_STAGE_TO_LEVEL = new int[] { -+ ChunkHolderManager.MAX_TICKET_LEVEL + 1, -+ LOADED_TICKET_LEVEL, -+ LOADED_TICKET_LEVEL, -+ GENERATED_TICKET_LEVEL, -+ GENERATED_TICKET_LEVEL, -+ TICK_TICKET_LEVEL -+ }; -+ private final Long2ByteOpenHashMap chunkTicketStage = new Long2ByteOpenHashMap(); -+ { -+ this.chunkTicketStage.defaultReturnValue(CHUNK_TICKET_STAGE_NONE); -+ } -+ -+ // rate limiting -+ private final AllocatingRateLimiter chunkSendLimiter = new AllocatingRateLimiter(); -+ private final AllocatingRateLimiter chunkLoadTicketLimiter = new AllocatingRateLimiter(); -+ private final AllocatingRateLimiter chunkGenerateTicketLimiter = new AllocatingRateLimiter(); -+ -+ // queues -+ private final LongComparator CLOSEST_MANHATTAN_DIST = (final long c1, final long c2) -> { -+ final int c1x = CoordinateUtils.getChunkX(c1); -+ final int c1z = CoordinateUtils.getChunkZ(c1); -+ -+ final int c2x = CoordinateUtils.getChunkX(c2); -+ final int c2z = CoordinateUtils.getChunkZ(c2); -+ -+ final int centerX = PlayerChunkLoaderData.this.lastChunkX; -+ final int centerZ = PlayerChunkLoaderData.this.lastChunkZ; -+ -+ return Integer.compare( -+ Math.abs(c1x - centerX) + Math.abs(c1z - centerZ), -+ Math.abs(c2x - centerX) + Math.abs(c2z - centerZ) -+ ); -+ }; -+ private final LongHeapPriorityQueue sendQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ private final LongHeapPriorityQueue tickingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ private final LongHeapPriorityQueue generatingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ private final LongHeapPriorityQueue genQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ private final LongHeapPriorityQueue loadingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ private final LongHeapPriorityQueue loadQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST); -+ -+ private volatile boolean removed; -+ -+ public PlayerChunkLoaderData(final ServerLevel world, final ServerPlayer player) { -+ this.world = world; -+ this.player = player; -+ } -+ -+ private void flushDelayedTicketOps() { -+ if (this.delayedTicketOps.isEmpty()) { -+ return; -+ } -+ this.world.chunkTaskScheduler.chunkHolderManager.pushDelayedTicketUpdates(this.delayedTicketOps); -+ this.delayedTicketOps.clear(); -+ this.world.chunkTaskScheduler.chunkHolderManager.tryDrainTicketUpdates(); -+ } -+ -+ private void pushDelayedTicketOp(final ChunkHolderManager.TicketOperation op) { -+ this.delayedTicketOps.addLast(op); -+ } -+ -+ private void sendChunk(final int chunkX, final int chunkZ) { -+ if (this.sentChunks.add(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -+ this.world.getChunkSource().chunkMap.updateChunkTracking(this.player, -+ new ChunkPos(chunkX, chunkZ), new MutableObject<>(), false, true); // unloaded, loaded -+ return; -+ } -+ throw new IllegalStateException(); -+ } -+ -+ private void sendUnloadChunk(final int chunkX, final int chunkZ) { -+ if (!this.sentChunks.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -+ return; -+ } -+ this.sendUnloadChunkRaw(chunkX, chunkZ); -+ } -+ -+ private void sendUnloadChunkRaw(final int chunkX, final int chunkZ) { -+ this.player.getLevel().getChunkSource().chunkMap.updateChunkTracking(this.player, -+ new ChunkPos(chunkX, chunkZ), null, true, false); // unloaded, loaded -+ } -+ -+ private final SingleUserAreaMap broadcastMap = new SingleUserAreaMap<>(this) { -+ @Override -+ protected void addCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ // do nothing, we only care about remove -+ } -+ -+ @Override -+ protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ parameter.sendUnloadChunk(chunkX, chunkZ); -+ } -+ }; -+ private final SingleUserAreaMap loadTicketCleanup = new SingleUserAreaMap<>(this) { -+ @Override -+ protected void addCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ // do nothing, we only care about remove -+ } -+ -+ @Override -+ protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ final byte ticketStage = parameter.chunkTicketStage.remove(chunk); -+ final int level = TICKET_STAGE_TO_LEVEL[ticketStage]; -+ if (level > ChunkHolderManager.MAX_TICKET_LEVEL) { -+ return; -+ } -+ -+ parameter.pushDelayedTicketOp(ChunkHolderManager.TicketOperation.addAndRemove( -+ chunk, -+ TicketType.UNKNOWN, level, new ChunkPos(chunkX, chunkZ), -+ REGION_PLAYER_TICKET, level, parameter.idBoxed -+ )); -+ } -+ }; -+ private final SingleUserAreaMap tickMap = new SingleUserAreaMap<>(this) { -+ @Override -+ protected void addCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ // do nothing, we will detect ticking chunks when we try to load them -+ } -+ -+ @Override -+ protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) { -+ final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ // note: by the time this is called, the tick cleanup should have ran - so, if the chunk is at -+ // the tick stage it was deemed in range for loading. Thus, we need to move it to generated -+ if (!parameter.chunkTicketStage.replace(chunk, CHUNK_TICKET_STAGE_TICK, CHUNK_TICKET_STAGE_GENERATED)) { -+ return; -+ } -+ -+ // Since we are possibly downgrading the ticket level, we add an unknown ticket so that -+ // the level is kept until tick(). -+ parameter.pushDelayedTicketOp(ChunkHolderManager.TicketOperation.addAndRemove( -+ chunk, -+ TicketType.UNKNOWN, TICK_TICKET_LEVEL, new ChunkPos(chunkX, chunkZ), -+ REGION_PLAYER_TICKET, TICK_TICKET_LEVEL, parameter.idBoxed -+ )); -+ // keep chunk at new generated level -+ parameter.pushDelayedTicketOp(ChunkHolderManager.TicketOperation.addOp( -+ chunk, -+ REGION_PLAYER_TICKET, GENERATED_TICKET_LEVEL, parameter.idBoxed -+ )); -+ } -+ }; -+ -+ private static boolean wantChunkLoaded(final int centerX, final int centerZ, final int chunkX, final int chunkZ, -+ final int sendRadius) { -+ // expect sendRadius to be = 1 + target viewable radius -+ return ChunkMap.isChunkInRange(chunkX, chunkZ, centerX, centerZ, sendRadius); -+ } -+ -+ private static int getClientViewDistance(final ServerPlayer player) { -+ final Integer vd = player.clientViewDistance; -+ return vd == null ? -1 : Math.max(0, vd.intValue()); -+ } -+ -+ private static int getTickDistance(final int playerTickViewDistance, final int worldTickViewDistance) { -+ return playerTickViewDistance < 0 ? worldTickViewDistance : playerTickViewDistance; -+ } -+ -+ private static int getLoadViewDistance(final int tickViewDistance, final int playerLoadViewDistance, -+ final int worldLoadViewDistance) { -+ return Math.max(tickViewDistance + 1, playerLoadViewDistance < 0 ? worldLoadViewDistance : playerLoadViewDistance); -+ } -+ -+ private static int getSendViewDistance(final int loadViewDistance, final int clientViewDistance, -+ final int playerSendViewDistance, final int worldSendViewDistance) { -+ return Math.min( -+ loadViewDistance, -+ playerSendViewDistance < 0 ? (!GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance || clientViewDistance < 0 ? (worldSendViewDistance < 0 ? loadViewDistance : worldSendViewDistance) : clientViewDistance + 1) : playerSendViewDistance -+ ); -+ } -+ -+ private Packet updateClientChunkRadius(final int radius) { -+ this.lastSentChunkRadius = radius; -+ return new ClientboundSetChunkCacheRadiusPacket(radius); -+ } -+ -+ private Packet updateClientSimulationDistance(final int distance) { -+ this.lastSentSimulationDistance = distance; -+ return new ClientboundSetSimulationDistancePacket(distance); -+ } -+ -+ private Packet updateClientChunkCenter(final int chunkX, final int chunkZ) { -+ this.lastSentChunkCenterX = chunkX; -+ this.lastSentChunkCenterZ = chunkZ; -+ return new ClientboundSetChunkCacheCenterPacket(chunkX, chunkZ); -+ } -+ -+ private boolean canPlayerGenerateChunks() { -+ return !this.player.isSpectator() || this.world.getGameRules().getBoolean(GameRules.RULE_SPECTATORSGENERATECHUNKS); -+ } -+ -+ private double getMaxChunkLoadRate() { -+ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate; -+ -+ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); -+ } -+ -+ private double getMaxChunkGenRate() { -+ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate; -+ -+ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); -+ } -+ -+ private double getMaxChunkSendRate() { -+ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate; -+ -+ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate); -+ } -+ -+ private long getMaxChunkLoads() { -+ final long radiusChunks = (2L * this.lastLoadDistance + 1L) * (2L * this.lastLoadDistance + 1L); -+ long configLimit = GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads; -+ if (configLimit == 0L) { -+ // by default, only allow 1/5th of the chunks in the view distance to be concurrently active -+ configLimit = Math.max(5L, radiusChunks / 5L); -+ } else if (configLimit < 0L) { -+ configLimit = Integer.MAX_VALUE; -+ } // else: use the value configured -+ configLimit = configLimit - this.loadingQueue.size(); -+ -+ return configLimit; -+ } -+ -+ private long getMaxChunkGenerates() { -+ final long radiusChunks = (2L * this.lastLoadDistance + 1L) * (2L * this.lastLoadDistance + 1L); -+ long configLimit = GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates; -+ if (configLimit == 0L) { -+ // by default, only allow 1/5th of the chunks in the view distance to be concurrently active -+ configLimit = Math.max(5L, radiusChunks / 5L); -+ } else if (configLimit < 0L) { -+ configLimit = Integer.MAX_VALUE; -+ } // else: use the value configured -+ configLimit = configLimit - this.generatingQueue.size(); -+ -+ return configLimit; -+ } -+ -+ private boolean wantChunkSent(final int chunkX, final int chunkZ) { -+ final int dx = this.lastChunkX - chunkX; -+ final int dz = this.lastChunkZ - chunkZ; -+ return Math.max(Math.abs(dx), Math.abs(dz)) <= this.lastSendDistance && wantChunkLoaded( -+ this.lastChunkX, this.lastChunkZ, chunkX, chunkZ, this.lastSendDistance -+ ); -+ } -+ -+ private boolean wantChunkTicked(final int chunkX, final int chunkZ) { -+ final int dx = this.lastChunkX - chunkX; -+ final int dz = this.lastChunkZ - chunkZ; -+ return Math.max(Math.abs(dx), Math.abs(dz)) <= this.lastTickDistance; -+ } -+ -+ void updateQueues(final long time) { -+ TickThread.ensureTickThread(this.player, "Cannot tick player chunk loader async"); -+ if (this.removed) { -+ throw new IllegalStateException("Ticking removed player chunk loader"); -+ } -+ // update rate limits -+ final double loadRate = this.getMaxChunkLoadRate(); -+ final double genRate = this.getMaxChunkGenRate(); -+ final double sendRate = this.getMaxChunkSendRate(); -+ -+ this.chunkLoadTicketLimiter.tickAllocation(time, loadRate, loadRate); -+ this.chunkGenerateTicketLimiter.tickAllocation(time, genRate, genRate); -+ this.chunkSendLimiter.tickAllocation(time, sendRate, sendRate); -+ -+ // try to progress chunk loads -+ while (!this.loadingQueue.isEmpty()) { -+ final long pendingLoadChunk = this.loadingQueue.firstLong(); -+ final int pendingChunkX = CoordinateUtils.getChunkX(pendingLoadChunk); -+ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingLoadChunk); -+ final ChunkAccess pending = this.world.chunkSource.getChunkAtImmediately(pendingChunkX, pendingChunkZ); -+ if (pending == null) { -+ // nothing to do here -+ break; -+ } -+ // chunk has loaded, so we can take it out of the queue -+ this.loadingQueue.dequeueLong(); -+ -+ // try to move to generate queue -+ final byte prev = this.chunkTicketStage.put(pendingLoadChunk, CHUNK_TICKET_STAGE_LOADED); -+ if (prev != CHUNK_TICKET_STAGE_LOADING) { -+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_LOADING + ", not " + prev); -+ } -+ -+ if (this.canGenerateChunks || this.isLoadedChunkGeneratable(pending)) { -+ this.genQueue.enqueue(pendingLoadChunk); -+ } // else: don't want to generate, so just leave it loaded -+ } -+ -+ // try to push more chunk loads -+ final long maxLoads = Math.max(0L, Math.min(MAX_RATE, Math.min(this.loadQueue.size(), this.getMaxChunkLoads()))); -+ final int maxLoadsThisTick = (int)this.chunkLoadTicketLimiter.takeAllocation(time, loadRate, maxLoads); -+ if (maxLoadsThisTick > 0) { -+ final LongArrayList chunks = new LongArrayList(maxLoadsThisTick); -+ for (int i = 0; i < maxLoadsThisTick; ++i) { -+ final long chunk = this.loadQueue.dequeueLong(); -+ final byte prev = this.chunkTicketStage.put(chunk, CHUNK_TICKET_STAGE_LOADING); -+ if (prev != CHUNK_TICKET_STAGE_NONE) { -+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_NONE + ", not " + prev); -+ } -+ this.pushDelayedTicketOp( -+ ChunkHolderManager.TicketOperation.addOp( -+ chunk, -+ REGION_PLAYER_TICKET, LOADED_TICKET_LEVEL, this.idBoxed -+ ) -+ ); -+ chunks.add(chunk); -+ this.loadingQueue.enqueue(chunk); -+ } -+ -+ // here we need to flush tickets, as scheduleChunkLoad requires tickets to be propagated with addTicket = false -+ this.flushDelayedTicketOps(); -+ // we only need to call scheduleChunkLoad because the loaded ticket level is not enough to start the chunk -+ // load - only generate ticket levels start anything, but they start generation... -+ // propagate levels -+ // Note: this CAN call plugin logic, so it is VITAL that our bookkeeping logic is completely done by the time this is invoked -+ this.world.chunkTaskScheduler.chunkHolderManager.processTicketUpdates(); -+ -+ if (this.removed) { -+ // process ticket updates may invoke plugin logic, which may remove this player -+ return; -+ } -+ -+ for (int i = 0; i < maxLoadsThisTick; ++i) { -+ final long queuedLoadChunk = chunks.getLong(i); -+ final int queuedChunkX = CoordinateUtils.getChunkX(queuedLoadChunk); -+ final int queuedChunkZ = CoordinateUtils.getChunkZ(queuedLoadChunk); -+ this.world.chunkTaskScheduler.scheduleChunkLoad( -+ queuedChunkX, queuedChunkZ, ChunkStatus.EMPTY, false, PrioritisedExecutor.Priority.NORMAL, null -+ ); -+ if (this.removed) { -+ return; -+ } -+ } -+ } -+ -+ // try to progress chunk generations -+ while (!this.generatingQueue.isEmpty()) { -+ final long pendingGenChunk = this.generatingQueue.firstLong(); -+ final int pendingChunkX = CoordinateUtils.getChunkX(pendingGenChunk); -+ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingGenChunk); -+ final LevelChunk pending = this.world.chunkSource.getChunkAtIfLoadedMainThreadNoCache(pendingChunkX, pendingChunkZ); -+ if (pending == null) { -+ // nothing to do here -+ break; -+ } -+ -+ // chunk has generated, so we can take it out of queue -+ this.generatingQueue.dequeueLong(); -+ -+ final byte prev = this.chunkTicketStage.put(pendingGenChunk, CHUNK_TICKET_STAGE_GENERATED); -+ if (prev != CHUNK_TICKET_STAGE_GENERATING) { -+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_GENERATING + ", not " + prev); -+ } -+ -+ // try to move to send queue -+ if (this.wantChunkSent(pendingChunkX, pendingChunkZ)) { -+ this.sendQueue.enqueue(pendingGenChunk); -+ } -+ // try to move to tick queue -+ if (this.wantChunkTicked(pendingChunkX, pendingChunkZ)) { -+ this.tickingQueue.enqueue(pendingGenChunk); -+ } -+ } -+ -+ // try to push more chunk generations -+ final long maxGens = Math.max(0L, Math.min(MAX_RATE, Math.min(this.genQueue.size(), this.getMaxChunkGenerates()))); -+ final int maxGensThisTick = (int)this.chunkGenerateTicketLimiter.takeAllocation(time, genRate, maxGens); -+ for (int i = 0; i < maxGensThisTick; ++i) { -+ final long chunk = this.genQueue.dequeueLong(); -+ final byte prev = this.chunkTicketStage.put(chunk, CHUNK_TICKET_STAGE_GENERATING); -+ if (prev != CHUNK_TICKET_STAGE_LOADED) { -+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_LOADED + ", not " + prev); -+ } -+ this.pushDelayedTicketOp( -+ ChunkHolderManager.TicketOperation.addAndRemove( -+ chunk, -+ REGION_PLAYER_TICKET, GENERATED_TICKET_LEVEL, this.idBoxed, -+ REGION_PLAYER_TICKET, LOADED_TICKET_LEVEL, this.idBoxed -+ ) -+ ); -+ this.generatingQueue.enqueue(chunk); -+ } -+ -+ // try to pull ticking chunks -+ tick_check_outer: -+ while (!this.tickingQueue.isEmpty()) { -+ final long pendingTicking = this.tickingQueue.firstLong(); -+ final int pendingChunkX = CoordinateUtils.getChunkX(pendingTicking); -+ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingTicking); -+ -+ final int tickingReq = 2; -+ for (int dz = -tickingReq; dz <= tickingReq; ++dz) { -+ for (int dx = -tickingReq; dx <= tickingReq; ++dx) { -+ if ((dx | dz) == 0) { -+ continue; -+ } -+ final long neighbour = CoordinateUtils.getChunkKey(dx + pendingChunkX, dz + pendingChunkZ); -+ final byte stage = this.chunkTicketStage.get(neighbour); -+ if (stage != CHUNK_TICKET_STAGE_GENERATED && stage != CHUNK_TICKET_STAGE_TICK) { -+ break tick_check_outer; -+ } -+ } -+ } -+ // only gets here if all neighbours were marked as generated or ticking themselves -+ this.tickingQueue.dequeueLong(); -+ this.pushDelayedTicketOp( -+ ChunkHolderManager.TicketOperation.addAndRemove( -+ pendingTicking, -+ REGION_PLAYER_TICKET, TICK_TICKET_LEVEL, this.idBoxed, -+ REGION_PLAYER_TICKET, GENERATED_TICKET_LEVEL, this.idBoxed -+ ) -+ ); -+ // there is no queue to add after ticking -+ final byte prev = this.chunkTicketStage.put(pendingTicking, CHUNK_TICKET_STAGE_TICK); -+ if (prev != CHUNK_TICKET_STAGE_GENERATED) { -+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_GENERATED + ", not " + prev); -+ } -+ } -+ -+ // try to pull sending chunks -+ final long maxSends = Math.max(0L, Math.min(MAX_RATE, Integer.MAX_VALUE)); // no logic to track concurrent sends -+ final int maxSendsThisTick = Math.min((int)this.chunkSendLimiter.takeAllocation(time, sendRate, maxSends), this.sendQueue.size()); -+ // we do not return sends that we took from the allocation back because we want to limit the max send rate, not target it -+ for (int i = 0; i < maxSendsThisTick; ++i) { -+ final long pendingSend = this.sendQueue.firstLong(); -+ final int pendingSendX = CoordinateUtils.getChunkX(pendingSend); -+ final int pendingSendZ = CoordinateUtils.getChunkZ(pendingSend); -+ final LevelChunk chunk = this.world.chunkSource.getChunkAtIfLoadedMainThreadNoCache(pendingSendX, pendingSendZ); -+ if (!chunk.areNeighboursLoaded(1) || !TickThread.isTickThreadFor(this.world, pendingSendX, pendingSendZ)) { -+ // nothing to do -+ // the target chunk may not be owned by this region, but this should be resolved in the future -+ break; -+ } -+ this.sendQueue.dequeueLong(); -+ -+ this.sendChunk(pendingSendX, pendingSendZ); -+ if (this.removed) { -+ // sendChunk may invoke plugin logic -+ return; -+ } -+ } -+ -+ this.flushDelayedTicketOps(); -+ // we assume propagate ticket levels happens after this call -+ } -+ -+ void add() { -+ TickThread.ensureTickThread(this.player, "Cannot add player asynchronously"); -+ if (this.removed) { -+ throw new IllegalStateException("Adding removed player chunk loader"); -+ } -+ final ViewDistances playerDistances = this.player.getViewDistances(); -+ final ViewDistances worldDistances = this.world.getViewDistances(); -+ final int chunkX = this.player.chunkPosition().x; -+ final int chunkZ = this.player.chunkPosition().z; -+ -+ final int tickViewDistance = getTickDistance(playerDistances.tickViewDistance, worldDistances.tickViewDistance); -+ // load view cannot be less-than tick view + 1 -+ final int loadViewDistance = getLoadViewDistance(tickViewDistance, playerDistances.loadViewDistance, worldDistances.loadViewDistance); -+ // send view cannot be greater-than load view -+ final int clientViewDistance = getClientViewDistance(this.player); -+ final int sendViewDistance = getSendViewDistance(loadViewDistance, clientViewDistance, playerDistances.sendViewDistance, worldDistances.sendViewDistance); -+ -+ // send view distances -+ this.player.connection.send(this.updateClientChunkRadius(sendViewDistance)); -+ this.player.connection.send(this.updateClientSimulationDistance(tickViewDistance)); -+ -+ // add to distance maps -+ this.broadcastMap.add(chunkX, chunkZ, sendViewDistance); -+ this.loadTicketCleanup.add(chunkX, chunkZ, loadViewDistance + 1); -+ this.tickMap.add(chunkX, chunkZ, tickViewDistance); -+ -+ // update chunk center -+ this.player.connection.send(this.updateClientChunkCenter(chunkX, chunkZ)); -+ -+ // now we can update -+ this.update(); -+ } -+ -+ private boolean isLoadedChunkGeneratable(final int chunkX, final int chunkZ) { -+ return this.isLoadedChunkGeneratable(this.world.chunkSource.getChunkAtImmediately(chunkX, chunkZ)); -+ } -+ -+ private boolean isLoadedChunkGeneratable(final ChunkAccess chunkAccess) { -+ final BelowZeroRetrogen belowZeroRetrogen; -+ return chunkAccess != null && ( -+ chunkAccess.getStatus() == ChunkStatus.FULL || -+ ((belowZeroRetrogen = chunkAccess.getBelowZeroRetrogen()) != null && belowZeroRetrogen.targetStatus().isOrAfter(ChunkStatus.FULL)) -+ ); -+ } -+ -+ void update() { -+ TickThread.ensureTickThread(this.player, "Cannot update player asynchronously"); -+ if (this.removed) { -+ throw new IllegalStateException("Updating removed player chunk loader"); -+ } -+ final ViewDistances playerDistances = this.player.getViewDistances(); -+ final ViewDistances worldDistances = this.world.getViewDistances(); -+ -+ final int tickViewDistance = getTickDistance(playerDistances.tickViewDistance, worldDistances.tickViewDistance); -+ // load view cannot be less-than tick view + 1 -+ final int loadViewDistance = getLoadViewDistance(tickViewDistance, playerDistances.loadViewDistance, worldDistances.loadViewDistance); -+ // send view cannot be greater-than load view -+ final int clientViewDistance = getClientViewDistance(this.player); -+ final int sendViewDistance = getSendViewDistance(loadViewDistance, clientViewDistance, playerDistances.sendViewDistance, worldDistances.sendViewDistance); -+ -+ final ChunkPos playerPos = this.player.chunkPosition(); -+ final boolean canGenerateChunks = this.canPlayerGenerateChunks(); -+ final int currentChunkX = playerPos.x; -+ final int currentChunkZ = playerPos.z; -+ -+ final int prevChunkX = this.lastChunkX; -+ final int prevChunkZ = this.lastChunkZ; -+ -+ if ( -+ // has view distance stayed the same? -+ sendViewDistance == this.lastSendDistance -+ && loadViewDistance == this.lastLoadDistance -+ && tickViewDistance == this.lastTickDistance -+ -+ // has our chunk stayed the same? -+ && prevChunkX == currentChunkX -+ && prevChunkZ == currentChunkZ -+ -+ // can we still generate chunks? -+ && this.canGenerateChunks == canGenerateChunks -+ ) { -+ // nothing we care about changed, so we're not re-calculating -+ return; -+ } -+ -+ // update distance maps -+ this.broadcastMap.update(currentChunkX, currentChunkZ, sendViewDistance); -+ this.loadTicketCleanup.update(currentChunkX, currentChunkZ, loadViewDistance + 1); -+ this.tickMap.update(currentChunkX, currentChunkZ, tickViewDistance); -+ if (sendViewDistance > loadViewDistance || tickViewDistance > (loadViewDistance - 1)) { -+ throw new IllegalStateException(); -+ } -+ -+ // update VDs for client -+ // this should be after the distance map updates, as they will send unload packets -+ if (this.lastSentChunkRadius != sendViewDistance) { -+ this.player.connection.send(this.updateClientChunkRadius(sendViewDistance)); -+ } -+ if (this.lastSentSimulationDistance != tickViewDistance) { -+ this.player.connection.send(this.updateClientSimulationDistance(tickViewDistance)); -+ } -+ -+ this.sendQueue.clear(); -+ this.tickingQueue.clear(); -+ this.generatingQueue.clear(); -+ this.genQueue.clear(); -+ this.loadingQueue.clear(); -+ this.loadQueue.clear(); -+ -+ this.lastChunkX = currentChunkX; -+ this.lastChunkZ = currentChunkZ; -+ this.lastSendDistance = sendViewDistance; -+ this.lastLoadDistance = loadViewDistance; -+ this.lastTickDistance = tickViewDistance; -+ this.canGenerateChunks = canGenerateChunks; -+ -+ // +1 since we need to load chunks +1 around the load view distance... -+ final long[] toIterate = SEARCH_RADIUS_ITERATION_LIST[loadViewDistance + 1]; -+ // the iteration order is by increasing manhattan distance - so, we do NOT need to -+ // sort anything in the queue! -+ for (final long deltaChunk : toIterate) { -+ final int dx = CoordinateUtils.getChunkX(deltaChunk); -+ final int dz = CoordinateUtils.getChunkZ(deltaChunk); -+ final int chunkX = dx + currentChunkX; -+ final int chunkZ = dz + currentChunkZ; -+ final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ final int squareDistance = Math.max(Math.abs(dx), Math.abs(dz)); -+ final int manhattanDistance = Math.abs(dx) + Math.abs(dz); -+ -+ // since chunk sending is not by radius alone, we need an extra check here to account for -+ // everything <= sendDistance -+ // Note: Vanilla may want to send chunks outside the send view distance, so we do need -+ // the dist <= view check -+ final boolean sendChunk = squareDistance <= sendViewDistance -+ && wantChunkLoaded(currentChunkX, currentChunkZ, chunkX, chunkZ, sendViewDistance); -+ final boolean sentChunk = sendChunk ? this.sentChunks.contains(chunk) : this.sentChunks.remove(chunk); -+ -+ if (!sendChunk && sentChunk) { -+ // have sent the chunk, but don't want it anymore -+ // unload it now -+ this.sendUnloadChunkRaw(chunkX, chunkZ); -+ } -+ -+ final byte stage = this.chunkTicketStage.get(chunk); -+ switch (stage) { -+ case CHUNK_TICKET_STAGE_NONE: { -+ // we want the chunk to be at least loaded -+ this.loadQueue.enqueue(chunk); -+ break; -+ } -+ case CHUNK_TICKET_STAGE_LOADING: { -+ this.loadingQueue.enqueue(chunk); -+ break; -+ } -+ case CHUNK_TICKET_STAGE_LOADED: { -+ if (canGenerateChunks || this.isLoadedChunkGeneratable(chunkX, chunkZ)) { -+ this.genQueue.enqueue(chunk); -+ } -+ break; -+ } -+ case CHUNK_TICKET_STAGE_GENERATING: { -+ this.generatingQueue.enqueue(chunk); -+ break; -+ } -+ case CHUNK_TICKET_STAGE_GENERATED: { -+ if (sendChunk && !sentChunk) { -+ this.sendQueue.enqueue(chunk); -+ } -+ if (squareDistance <= tickViewDistance) { -+ this.tickingQueue.enqueue(chunk); -+ } -+ break; -+ } -+ case CHUNK_TICKET_STAGE_TICK: { -+ if (sendChunk && !sentChunk) { -+ this.sendQueue.enqueue(chunk); -+ } -+ break; -+ } -+ default: { -+ throw new IllegalStateException("Unknown stage: " + stage); -+ } -+ } -+ } -+ -+ // update the chunk center -+ // this must be done last so that the client does not ignore any of our unload chunk packets above -+ if (this.lastSentChunkCenterX != currentChunkX || this.lastSentChunkCenterZ != currentChunkZ) { -+ this.player.connection.send(this.updateClientChunkCenter(currentChunkX, currentChunkZ)); -+ } -+ -+ this.flushDelayedTicketOps(); -+ } -+ -+ void remove() { -+ TickThread.ensureTickThread(this.player, "Cannot add player asynchronously"); -+ if (this.removed) { -+ throw new IllegalStateException("Removing removed player chunk loader"); -+ } -+ this.removed = true; -+ // sends the chunk unload packets -+ this.broadcastMap.remove(); -+ // cleans up loading/generating tickets -+ this.loadTicketCleanup.remove(); -+ // cleans up ticking tickets -+ this.tickMap.remove(); -+ -+ // purge queues -+ this.sendQueue.clear(); -+ this.tickingQueue.clear(); -+ this.generatingQueue.clear(); -+ this.genQueue.clear(); -+ this.loadingQueue.clear(); -+ this.loadQueue.clear(); -+ -+ // flush ticket changes -+ this.flushDelayedTicketOps(); -+ -+ // now all tickets should be removed, which is all of our external state -+ } -+ } -+ -+ // TODO rebase into util patch -+ private static final class AllocatingRateLimiter { -+ -+ // max difference granularity in ns -+ private static final long MAX_GRANULARITY = TimeUnit.SECONDS.toNanos(1L); -+ -+ private double allocation; -+ private long lastAllocationUpdate; -+ private double takeCarry; -+ private long lastTakeUpdate; -+ -+ // rate in units/s, and time in ns -+ public void tickAllocation(final long time, final double rate, final double maxAllocation) { -+ final long diff = Math.min(MAX_GRANULARITY, time - this.lastAllocationUpdate); -+ this.lastAllocationUpdate = time; -+ -+ this.allocation = Math.min(maxAllocation - this.takeCarry, this.allocation + rate * (diff*1.0E-9D)); -+ } -+ -+ // rate in units/s, and time in ns -+ public long takeAllocation(final long time, final double rate, final long maxTake) { -+ if (maxTake < 1L) { -+ return 0L; -+ } -+ -+ double ret = this.takeCarry; -+ final long diff = Math.min(MAX_GRANULARITY, time - this.lastTakeUpdate); -+ this.lastTakeUpdate = time; -+ -+ // note: abs(takeCarry) <= 1.0 -+ final double take = Math.min(Math.min((double)maxTake - this.takeCarry, this.allocation), rate * (diff*1.0E-9)); -+ -+ ret += take; -+ this.allocation -= take; -+ -+ final long retInteger = (long)Math.floor(ret); -+ this.takeCarry = ret - (double)retInteger; -+ -+ return retInteger; -+ } -+ } -+ -+ public static abstract class SingleUserAreaMap { -+ -+ private static final int NOT_SET = Integer.MIN_VALUE; -+ -+ private final T parameter; -+ private int lastChunkX = NOT_SET; -+ private int lastChunkZ = NOT_SET; -+ private int distance = NOT_SET; -+ -+ public SingleUserAreaMap(final T parameter) { -+ this.parameter = parameter; -+ } -+ -+ /* math sign function except 0 returns 1 */ -+ protected static int sign(int val) { -+ return 1 | (val >> (Integer.SIZE - 1)); -+ } -+ -+ protected abstract void addCallback(final T parameter, final int chunkX, final int chunkZ); -+ -+ protected abstract void removeCallback(final T parameter, final int chunkX, final int chunkZ); -+ -+ private void addToNew(final T parameter, final int chunkX, final int chunkZ, final int distance) { -+ final int maxX = chunkX + distance; -+ final int maxZ = chunkZ + distance; -+ -+ for (int cx = chunkX - distance; cx <= maxX; ++cx) { -+ for (int cz = chunkZ - distance; cz <= maxZ; ++cz) { -+ this.addCallback(parameter, cx, cz); -+ } -+ } -+ } -+ -+ private void removeFromOld(final T parameter, final int chunkX, final int chunkZ, final int distance) { -+ final int maxX = chunkX + distance; -+ final int maxZ = chunkZ + distance; -+ -+ for (int cx = chunkX - distance; cx <= maxX; ++cx) { -+ for (int cz = chunkZ - distance; cz <= maxZ; ++cz) { -+ this.removeCallback(parameter, cx, cz); -+ } -+ } -+ } -+ -+ public final boolean add(final int chunkX, final int chunkZ, final int distance) { -+ if (distance < 0) { -+ throw new IllegalArgumentException(Integer.toString(distance)); -+ } -+ if (this.lastChunkX != NOT_SET) { -+ return false; -+ } -+ this.lastChunkX = chunkX; -+ this.lastChunkZ = chunkZ; -+ this.distance = distance; -+ -+ this.addToNew(this.parameter, chunkX, chunkZ, distance); -+ -+ return true; -+ } -+ -+ public final boolean update(final int toX, final int toZ, final int newViewDistance) { -+ if (newViewDistance < 0) { -+ throw new IllegalArgumentException(Integer.toString(newViewDistance)); -+ } -+ final int fromX = this.lastChunkX; -+ final int fromZ = this.lastChunkZ; -+ final int oldViewDistance = this.distance; -+ if (fromX == NOT_SET) { -+ return false; -+ } -+ -+ this.lastChunkX = toX; -+ this.lastChunkZ = toZ; -+ -+ final T parameter = this.parameter; -+ -+ -+ final int dx = toX - fromX; -+ final int dz = toZ - fromZ; -+ -+ final int totalX = IntegerUtil.branchlessAbs(fromX - toX); -+ final int totalZ = IntegerUtil.branchlessAbs(fromZ - toZ); -+ -+ if (Math.max(totalX, totalZ) > (2 * Math.max(newViewDistance, oldViewDistance))) { -+ // teleported? -+ this.removeFromOld(parameter, fromX, fromZ, oldViewDistance); -+ this.addToNew(parameter, toX, toZ, newViewDistance); -+ return true; -+ } -+ -+ if (oldViewDistance != newViewDistance) { -+ // remove loop -+ -+ final int oldMinX = fromX - oldViewDistance; -+ final int oldMinZ = fromZ - oldViewDistance; -+ final int oldMaxX = fromX + oldViewDistance; -+ final int oldMaxZ = fromZ + oldViewDistance; -+ for (int currX = oldMinX; currX <= oldMaxX; ++currX) { -+ for (int currZ = oldMinZ; currZ <= oldMaxZ; ++currZ) { -+ -+ // only remove if we're outside the new view distance... -+ if (Math.max(IntegerUtil.branchlessAbs(currX - toX), IntegerUtil.branchlessAbs(currZ - toZ)) > newViewDistance) { -+ this.removeCallback(parameter, currX, currZ); -+ } -+ } -+ } -+ -+ // add loop -+ -+ final int newMinX = toX - newViewDistance; -+ final int newMinZ = toZ - newViewDistance; -+ final int newMaxX = toX + newViewDistance; -+ final int newMaxZ = toZ + newViewDistance; -+ for (int currX = newMinX; currX <= newMaxX; ++currX) { -+ for (int currZ = newMinZ; currZ <= newMaxZ; ++currZ) { -+ -+ // only add if we're outside the old view distance... -+ if (Math.max(IntegerUtil.branchlessAbs(currX - fromX), IntegerUtil.branchlessAbs(currZ - fromZ)) > oldViewDistance) { -+ this.addCallback(parameter, currX, currZ); -+ } -+ } -+ } -+ -+ return true; -+ } -+ -+ // x axis is width -+ // z axis is height -+ // right refers to the x axis of where we moved -+ // top refers to the z axis of where we moved -+ -+ // same view distance -+ -+ // used for relative positioning -+ final int up = sign(dz); // 1 if dz >= 0, -1 otherwise -+ final int right = sign(dx); // 1 if dx >= 0, -1 otherwise -+ -+ // The area excluded by overlapping the two view distance squares creates four rectangles: -+ // Two on the left, and two on the right. The ones on the left we consider the "removed" section -+ // and on the right the "added" section. -+ // https://i.imgur.com/MrnOBgI.png is a reference image. Note that the outside border is not actually -+ // exclusive to the regions they surround. -+ -+ // 4 points of the rectangle -+ int maxX; // exclusive -+ int minX; // inclusive -+ int maxZ; // exclusive -+ int minZ; // inclusive -+ -+ if (dx != 0) { -+ // handle right addition -+ -+ maxX = toX + (oldViewDistance * right) + right; // exclusive -+ minX = fromX + (oldViewDistance * right) + right; // inclusive -+ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive -+ minZ = toZ - (oldViewDistance * up); // inclusive -+ -+ for (int currX = minX; currX != maxX; currX += right) { -+ for (int currZ = minZ; currZ != maxZ; currZ += up) { -+ this.addCallback(parameter, currX, currZ); -+ } -+ } -+ } -+ -+ if (dz != 0) { -+ // handle up addition -+ -+ maxX = toX + (oldViewDistance * right) + right; // exclusive -+ minX = toX - (oldViewDistance * right); // inclusive -+ maxZ = toZ + (oldViewDistance * up) + up; // exclusive -+ minZ = fromZ + (oldViewDistance * up) + up; // inclusive -+ -+ for (int currX = minX; currX != maxX; currX += right) { -+ for (int currZ = minZ; currZ != maxZ; currZ += up) { -+ this.addCallback(parameter, currX, currZ); -+ } -+ } -+ } -+ -+ if (dx != 0) { -+ // handle left removal -+ -+ maxX = toX - (oldViewDistance * right); // exclusive -+ minX = fromX - (oldViewDistance * right); // inclusive -+ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive -+ minZ = toZ - (oldViewDistance * up); // inclusive -+ -+ for (int currX = minX; currX != maxX; currX += right) { -+ for (int currZ = minZ; currZ != maxZ; currZ += up) { -+ this.removeCallback(parameter, currX, currZ); -+ } -+ } -+ } -+ -+ if (dz != 0) { -+ // handle down removal -+ -+ maxX = fromX + (oldViewDistance * right) + right; // exclusive -+ minX = fromX - (oldViewDistance * right); // inclusive -+ maxZ = toZ - (oldViewDistance * up); // exclusive -+ minZ = fromZ - (oldViewDistance * up); // inclusive -+ -+ for (int currX = minX; currX != maxX; currX += right) { -+ for (int currZ = minZ; currZ != maxZ; currZ += up) { -+ this.removeCallback(parameter, currX, currZ); -+ } -+ } -+ } -+ -+ return true; -+ } -+ -+ public final boolean remove() { -+ final int chunkX = this.lastChunkX; -+ final int chunkZ = this.lastChunkZ; -+ final int distance = this.distance; -+ if (chunkX == NOT_SET) { -+ return false; -+ } -+ -+ this.lastChunkX = this.lastChunkZ = this.distance = NOT_SET; -+ -+ this.removeFromOld(this.parameter, chunkX, chunkZ, distance); -+ -+ return true; -+ } -+ } -+ -+ static final class CountedSRSWLinkedQueue { -+ -+ private final SRSWLinkedQueue queue = new SRSWLinkedQueue<>(); -+ private volatile long countAdded; -+ private volatile long countRemoved; -+ -+ private static final VarHandle COUNT_ADDED_HANDLE = ConcurrentUtil.getVarHandle(CountedSRSWLinkedQueue.class, "countAdded", long.class); -+ private static final VarHandle COUNT_REMOVED_HANDLE = ConcurrentUtil.getVarHandle(CountedSRSWLinkedQueue.class, "countRemoved", long.class); -+ -+ private long getCountAddedPlain() { -+ return (long)COUNT_ADDED_HANDLE.get(this); -+ } -+ -+ private long getCountAddedAcquire() { -+ return (long)COUNT_ADDED_HANDLE.getAcquire(this); -+ } -+ -+ private void setCountAddedRelease(final long to) { -+ COUNT_ADDED_HANDLE.setRelease(this, to); -+ } -+ -+ private long getCountRemovedPlain() { -+ return (long)COUNT_REMOVED_HANDLE.get(this); -+ } -+ -+ private long getCountRemovedAcquire() { -+ return (long)COUNT_REMOVED_HANDLE.getAcquire(this); -+ } -+ -+ private void setCountRemovedRelease(final long to) { -+ COUNT_REMOVED_HANDLE.setRelease(this, to); -+ } -+ -+ public void add(final E element) { -+ this.setCountAddedRelease(this.getCountAddedPlain() + 1L); -+ this.queue.addLast(element); -+ } -+ -+ public E poll() { -+ final E ret = this.queue.poll(); -+ if (ret != null) { -+ this.setCountRemovedRelease(this.getCountRemovedPlain() + 1L); -+ } -+ -+ return ret; -+ } -+ -+ public long size() { -+ final long removed = this.getCountRemovedAcquire(); -+ final long added = this.getCountAddedAcquire(); -+ -+ return added - removed; -+ } -+ } -+} -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 ad3560284ae79b9c6bbc8752be7d9d14b18e226e..cb12c31ffe014e17eb9f901ab0a273802e3e0245 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 -@@ -1,5 +1,6 @@ - package io.papermc.paper.chunk.system.scheduling; - -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; - import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; - import ca.spottedleaf.concurrentutil.map.SWMRLong2ObjectHashTable; - import co.aikar.timings.Timing; -@@ -500,6 +501,21 @@ public final class ChunkHolderManager { - } - } - -+ // atomic with respect to all add/remove/addandremove ticket calls for the given chunk -+ public boolean addIfRemovedTicket(final long chunk, final TicketType addType, final int addLevel, final T addIdentifier, -+ final TicketType removeType, final int removeLevel, final V removeIdentifier) { -+ this.ticketLock.lock(); -+ try { -+ if (this.removeTicketAtLevel(removeType, chunk, removeLevel, removeIdentifier)) { -+ this.addTicketAtLevel(addType, chunk, addLevel, addIdentifier); -+ return true; -+ } -+ return false; -+ } finally { -+ this.ticketLock.unlock(); -+ } -+ } -+ - public void removeAllTicketsFor(final TicketType ticketType, final int ticketLevel, final T ticketIdentifier) { - if (ticketLevel > MAX_TICKET_LEVEL) { - return; -@@ -907,6 +923,142 @@ public final class ChunkHolderManager { - } - } - -+ public enum TicketOperationType { -+ ADD, REMOVE, ADD_IF_REMOVED, ADD_AND_REMOVE -+ } -+ -+ public static record TicketOperation ( -+ TicketOperationType op, long chunkCoord, -+ TicketType ticketType, int ticketLevel, T identifier, -+ TicketType ticketType2, int ticketLevel2, V identifier2 -+ ) { -+ -+ private TicketOperation(TicketOperationType op, long chunkCoord, -+ TicketType ticketType, int ticketLevel, T identifier) { -+ this(op, chunkCoord, ticketType, ticketLevel, identifier, null, 0, null); -+ } -+ -+ public static TicketOperation addOp(final ChunkPos chunk, final TicketType type, final int ticketLevel, final T identifier) { -+ return addOp(CoordinateUtils.getChunkKey(chunk), type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation addOp(final int chunkX, final int chunkZ, final TicketType type, final int ticketLevel, final T identifier) { -+ return addOp(CoordinateUtils.getChunkKey(chunkX, chunkZ), type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation addOp(final long chunk, final TicketType type, final int ticketLevel, final T identifier) { -+ return new TicketOperation<>(TicketOperationType.ADD, chunk, type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation removeOp(final ChunkPos chunk, final TicketType type, final int ticketLevel, final T identifier) { -+ return removeOp(CoordinateUtils.getChunkKey(chunk), type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation removeOp(final int chunkX, final int chunkZ, final TicketType type, final int ticketLevel, final T identifier) { -+ return removeOp(CoordinateUtils.getChunkKey(chunkX, chunkZ), type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation removeOp(final long chunk, final TicketType type, final int ticketLevel, final T identifier) { -+ return new TicketOperation<>(TicketOperationType.REMOVE, chunk, type, ticketLevel, identifier); -+ } -+ -+ public static TicketOperation addIfRemovedOp(final long chunk, -+ final TicketType addType, final int addLevel, final T addIdentifier, -+ final TicketType removeType, final int removeLevel, final V removeIdentifier) { -+ return new TicketOperation<>( -+ TicketOperationType.ADD_IF_REMOVED, chunk, addType, addLevel, addIdentifier, -+ removeType, removeLevel, removeIdentifier -+ ); -+ } -+ -+ public static TicketOperation addAndRemove(final long chunk, -+ final TicketType addType, final int addLevel, final T addIdentifier, -+ final TicketType removeType, final int removeLevel, final V removeIdentifier) { -+ return new TicketOperation<>( -+ TicketOperationType.ADD_AND_REMOVE, chunk, addType, addLevel, addIdentifier, -+ removeType, removeLevel, removeIdentifier -+ ); -+ } -+ } -+ -+ private final MultiThreadedQueue> delayedTicketUpdates = new MultiThreadedQueue<>(); -+ -+ // note: MUST hold ticket lock, otherwise operation ordering is lost -+ private boolean drainTicketUpdates() { -+ boolean ret = false; -+ -+ TicketOperation operation; -+ while ((operation = this.delayedTicketUpdates.poll()) != null) { -+ switch (operation.op) { -+ case ADD: { -+ ret |= this.addTicketAtLevel(operation.ticketType, operation.chunkCoord, operation.ticketLevel, operation.identifier); -+ break; -+ } -+ case REMOVE: { -+ ret |= this.removeTicketAtLevel(operation.ticketType, operation.chunkCoord, operation.ticketLevel, operation.identifier); -+ break; -+ } -+ case ADD_IF_REMOVED: { -+ ret |= this.addIfRemovedTicket( -+ operation.chunkCoord, -+ operation.ticketType, operation.ticketLevel, operation.identifier, -+ operation.ticketType2, operation.ticketLevel2, operation.identifier2 -+ ); -+ break; -+ } -+ case ADD_AND_REMOVE: { -+ ret = true; -+ this.addAndRemoveTickets( -+ operation.chunkCoord, -+ operation.ticketType, operation.ticketLevel, operation.identifier, -+ operation.ticketType2, operation.ticketLevel2, operation.identifier2 -+ ); -+ break; -+ } -+ } -+ } -+ -+ return ret; -+ } -+ -+ public Boolean tryDrainTicketUpdates() { -+ final boolean acquired = this.ticketLock.tryLock(); -+ try { -+ if (!acquired) { -+ return null; -+ } -+ -+ return Boolean.valueOf(this.drainTicketUpdates()); -+ } finally { -+ if (acquired) { -+ this.ticketLock.unlock(); -+ } -+ } -+ } -+ -+ public void pushDelayedTicketUpdate(final TicketOperation operation) { -+ this.delayedTicketUpdates.add(operation); -+ } -+ -+ public void pushDelayedTicketUpdates(final Collection> operations) { -+ this.delayedTicketUpdates.addAll(operations); -+ } -+ -+ public Boolean tryProcessTicketUpdates() { -+ final boolean acquired = this.ticketLock.tryLock(); -+ try { -+ if (!acquired) { -+ return null; -+ } -+ -+ return Boolean.valueOf(this.processTicketUpdates(false, true, null)); -+ } finally { -+ if (acquired) { -+ this.ticketLock.unlock(); -+ } -+ } -+ } -+ - private final ThreadLocal BLOCK_TICKET_UPDATES = ThreadLocal.withInitial(() -> { - return Boolean.FALSE; - }); -@@ -955,6 +1107,8 @@ public final class ChunkHolderManager { - - this.ticketLock.lock(); - try { -+ this.drainTicketUpdates(); -+ - final boolean levelsUpdated = this.ticketLevelPropagator.propagateUpdates(); - if (levelsUpdated) { - // Unlike CB, ticket level updates cannot happen recursively. Thank god. -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java -index 84cc9397237fa0c17aa1012dfb5683c90eb6d3b8..e944bd32df98a1dd7dde8c5ce3698b6d81862cbf 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java -@@ -48,6 +48,10 @@ public final class ChunkTaskScheduler { - - public static ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool workerThreads; - -+ public static int getWorkerCount() { -+ return workerThreads.getThreadCount(); -+ } -+ - private static boolean initialised = false; - - public static void init(final GlobalConfiguration.ChunkSystem config) { -diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -index 8d442c5a498ecf288a0cc0c54889c6e2fda849ce..9f5f0d8ddc8f480b48079c70e38c9c08eff403f6 100644 ---- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -287,4 +287,43 @@ public class GlobalConfiguration extends ConfigurationPart { - public boolean useDimensionTypeForCustomSpawners = false; - public boolean strictAdvancementDimensionCheck = false; - } -+ -+ public ChunkLoadingBasic chunkLoadingBasic; -+ -+ public class ChunkLoadingBasic extends ConfigurationPart { -+ @Comment("The maximum rate in chunks per second that the server will send to any individual player. Set to -1 to disable this limit.") -+ public double playerMaxChunkSendRate = 75.0; -+ -+ @Comment( -+ "The maximum rate at which chunks will load for any individual player. " + -+ "Note that this setting also affects chunk generations, since a chunk load is always first issued to test if a" + -+ "chunk is already generated. Set to -1 to disable this limit." -+ ) -+ public double playerMaxChunkLoadRate = 100.0; -+ -+ @Comment("The maximum rate at which chunks will generate for any individual player. Set to -1 to disable this limit.") -+ public double playerMaxChunkGenerateRate = -1.0; -+ } -+ -+ public ChunkLoadingAdvanced chunkLoadingAdvanced; -+ -+ public class ChunkLoadingAdvanced extends ConfigurationPart { -+ @Comment( -+ "Set to true if the server will match the chunk send radius that clients have configured" + -+ "in their view distance settings if the client is less-than the server's send distance." -+ ) -+ public boolean autoConfigSendDistance = true; -+ -+ @Comment( -+ "Specifies the maximum amount of concurrent chunk loads that an individual player can have." + -+ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit." -+ ) -+ public int playerMaxConcurrentChunkLoads = 0; -+ -+ @Comment( -+ "Specifies the maximum amount of concurrent chunk generations that an individual player can have." + -+ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit." -+ ) -+ public int playerMaxConcurrentChunkGenerates = 0; -+ } - } -diff --git a/src/main/java/io/papermc/paper/util/IntervalledCounter.java b/src/main/java/io/papermc/paper/util/IntervalledCounter.java -index cea9c098ade00ee87b8efc8164ab72f5279758f0..197224e31175252d8438a8df585bbb65f2288d7f 100644 ---- a/src/main/java/io/papermc/paper/util/IntervalledCounter.java -+++ b/src/main/java/io/papermc/paper/util/IntervalledCounter.java -@@ -2,6 +2,8 @@ package io.papermc.paper.util; - - public final class IntervalledCounter { - -+ private static final int INITIAL_SIZE = 8; -+ - protected long[] times; - protected long[] counts; - protected final long interval; -@@ -11,8 +13,8 @@ public final class IntervalledCounter { - protected int tail; // exclusive - - public IntervalledCounter(final long interval) { -- this.times = new long[8]; -- this.counts = new long[8]; -+ this.times = new long[INITIAL_SIZE]; -+ this.counts = new long[INITIAL_SIZE]; - this.interval = interval; - } - -@@ -67,13 +69,13 @@ public final class IntervalledCounter { - this.tail = nextTail; - } - -- public void updateAndAdd(final int count) { -+ public void updateAndAdd(final long count) { - final long currTime = System.nanoTime(); - this.updateCurrentTime(currTime); - this.addTime(currTime, count); - } - -- public void updateAndAdd(final int count, final long currTime) { -+ public void updateAndAdd(final long count, final long currTime) { - this.updateCurrentTime(currTime); - this.addTime(currTime, count); - } -@@ -93,9 +95,13 @@ public final class IntervalledCounter { - this.tail = size; - - if (tail >= head) { -+ // sequentially ordered from [head, tail) - System.arraycopy(oldElements, head, newElements, 0, size); - System.arraycopy(oldCounts, head, newCounts, 0, size); - } else { -+ // ordered from [head, length) -+ // then followed by [0, tail) -+ - System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head); - System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail); - -@@ -106,10 +112,18 @@ public final class IntervalledCounter { - - // returns in units per second - public double getRate() { -- return this.size() / (this.interval * 1.0e-9); -+ return (double)this.sum / ((double)this.interval * 1.0E-9); -+ } -+ -+ public long getInterval() { -+ return this.interval; - } - -- public long size() { -+ public long getSum() { - return this.sum; - } -+ -+ public int totalDataPoints() { -+ return this.tail >= this.head ? (this.tail - this.head) : (this.tail + (this.counts.length - this.head)); -+ } - } -diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java -index 6efb8b10f17c70b05128039376d254e6beda3841..c856a9a0d085b278da416c59996fc131811f790c 100644 ---- a/src/main/java/io/papermc/paper/util/MCUtil.java -+++ b/src/main/java/io/papermc/paper/util/MCUtil.java -@@ -607,8 +607,8 @@ public final class MCUtil { - - worldData.addProperty("is-loaded", loadedWorlds.contains(bukkitWorld)); - worldData.addProperty("name", world.getWorld().getName()); -- worldData.addProperty("view-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance()); // Paper - replace chunk loader system -- worldData.addProperty("tick-view-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance()); // Paper - replace chunk loader system -+ worldData.addProperty("view-distance", world.getWorld().getViewDistance()); // Paper - replace chunk loader system -+ worldData.addProperty("tick-view-distance", world.getWorld().getSimulationDistance()); // Paper - replace chunk loader system - worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory); - worldData.addProperty("keep-spawn-loaded-range", world.paperConfig().spawn.keepSpawnLoadedRange * 16); - -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index 904fcdeb7937d36208cc9a8d5eca9ef3a5b2cd9e..a7013fdd8d82f24cadd90ea7e9ebc7c1501042d3 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -128,6 +128,26 @@ public class ChunkHolder { - com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInChunkTickRange; - // Paper end - optimise anyPlayerCloseEnoughForSpawning - -+ // Paper start - replace player chunk loader -+ private final com.destroystokyo.paper.util.maplist.ReferenceList playersSentChunkTo = new com.destroystokyo.paper.util.maplist.ReferenceList<>(); -+ -+ public void addPlayer(ServerPlayer player) { -+ if (!this.playersSentChunkTo.add(player)) { -+ throw new IllegalStateException("Already sent chunk " + this.pos + " in world '" + this.chunkMap.level.getWorld().getName() + "' to player " + player); -+ } -+ } -+ -+ public void removePlayer(ServerPlayer player) { -+ if (!this.playersSentChunkTo.remove(player)) { -+ throw new IllegalStateException("Have not sent chunk " + this.pos + " in world '" + this.chunkMap.level.getWorld().getName() + "' to player " + player); -+ } -+ } -+ -+ public boolean hasChunkBeenSent() { -+ return this.playersSentChunkTo.size() != 0; -+ } -+ // Paper end - replace player chunk loader -+ - public ChunkHolder(ChunkPos pos, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.PlayerProvider playersWatchingChunkProvider, io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder) { // Paper - rewrite chunk system - this.newChunkHolder = newChunkHolder; // Paper - rewrite chunk system - this.chunkToSaveHistory = null; -@@ -225,6 +245,11 @@ public class ChunkHolder { - // Paper - rewrite chunk system - - public void blockChanged(BlockPos pos) { -+ // Paper start - replace player chunk loader -+ if (this.playersSentChunkTo.size() == 0) { -+ return; -+ } -+ // Paper end - replace player chunk loader - LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance - - if (chunk != null) { -@@ -251,7 +276,7 @@ public class ChunkHolder { - LevelChunk chunk = this.getSendingChunk(); - // Paper end - no-tick view distance - -- if (chunk != null) { -+ if (this.playersSentChunkTo.size() != 0 && chunk != null) { // Paper - replace player chunk loader - int j = this.lightEngine.getMinLightSection(); - int k = this.lightEngine.getMaxLightSection(); - -@@ -351,27 +376,32 @@ public class ChunkHolder { - - } - -- public void broadcast(Packet packet, boolean onlyOnWatchDistanceEdge) { -- // Paper start - per player view distance -- // there can be potential desync with player's last mapped section and the view distance map, so use the -- // view distance map here. -- com.destroystokyo.paper.util.misc.PlayerAreaMap viewDistanceMap = this.chunkMap.playerChunkManager.broadcastMap; // Paper - replace old player chunk manager -- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = viewDistanceMap.getObjectsInRange(this.pos); -- if (players == null) { -- return; -- } -+ // Paper start - rewrite player chunk loader -+ public List getPlayers(boolean onlyOnWatchDistanceEdge) { -+ List ret = new java.util.ArrayList<>(); - -- Object[] backingSet = players.getBackingSet(); -- for (int i = 0, len = backingSet.length; i < len; ++i) { -- if (!(backingSet[i] instanceof ServerPlayer player)) { -+ for (int i = 0, len = this.playersSentChunkTo.size(); i < len; ++i) { -+ ServerPlayer player = this.playersSentChunkTo.getUnchecked(i); -+ if (onlyOnWatchDistanceEdge && !this.chunkMap.level.playerChunkLoader.isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) { - continue; - } -- if (!this.chunkMap.playerChunkManager.isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) { -+ ret.add(player); -+ } -+ -+ return ret; -+ } -+ // Paper end - rewrite player chunk loader -+ -+ public void broadcast(Packet packet, boolean onlyOnWatchDistanceEdge) { -+ // Paper start - rewrite player chunk loader - modeled after the above -+ for (int i = 0, len = this.playersSentChunkTo.size(); i < len; ++i) { -+ ServerPlayer player = this.playersSentChunkTo.getUnchecked(i); -+ if (onlyOnWatchDistanceEdge && !this.chunkMap.level.playerChunkLoader.isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) { - continue; - } - player.connection.send(packet); - } -- // Paper end - per player view distance -+ // Paper end - rewrite player chunk loader - } - - // Paper - rewrite chunk system -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index fbe209a66c77c47935ad026dd3e45e682af91fd8..78ccfe201f91b9d969766a6aed4ab033a8e1218f 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -199,7 +199,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // Paper end - use distance map to optimise tracker - - void addPlayerToDistanceMaps(ServerPlayer player) { -- this.playerChunkManager.addPlayer(player); // Paper - replace chunk loader -+ this.level.playerChunkLoader.addPlayer(player); // Paper - replace chunk loader - int chunkX = MCUtil.getChunkCoordinate(player.getX()); - int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); - // Note: players need to be explicitly added to distance maps before they can be updated -@@ -221,7 +221,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - void removePlayerFromDistanceMaps(ServerPlayer player) { -- this.playerChunkManager.removePlayer(player); // Paper - replace chunk loader -+ this.level.playerChunkLoader.removePlayer(player); // Paper - replace chunk loader - - // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning - this.playerMobSpawnMap.remove(player); -@@ -244,7 +244,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - int chunkX = MCUtil.getChunkCoordinate(player.getX()); - int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); - // Note: players need to be explicitly added to distance maps before they can be updated -- this.playerChunkManager.updatePlayer(player); // Paper - replace chunk loader -+ this.level.playerChunkLoader.updatePlayer(player); // Paper - replace chunk loader - this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning - // Paper start - per player mob spawning - if (this.playerMobDistanceMap != null) { -@@ -816,7 +816,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - // Paper start - replace player loader system - public void setTickViewDistance(int distance) { -- this.playerChunkManager.setTickDistance(distance); -+ this.level.playerChunkLoader.setTickDistance(distance); -+ } -+ -+ public void setSendViewDistance(int distance) { -+ this.level.playerChunkLoader.setSendDistance(distance); - } - // Paper end - replace player loader system - public void setViewDistance(int watchDistance) { -@@ -826,20 +830,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - int k = this.viewDistance; - - this.viewDistance = j; -- this.playerChunkManager.setLoadDistance(this.viewDistance); // Paper - replace player loader system -+ this.level.playerChunkLoader.setLoadDistance(this.viewDistance); // Paper - replace player loader system - } - - } - - public void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject> packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { // Paper - public // Paper - Anti-Xray - Bypass -+ io.papermc.paper.util.TickThread.ensureTickThread(this.level, pos, "May not update chunk tracking for chunk async"); // Paper - replace chunk loader system -+ io.papermc.paper.util.TickThread.ensureTickThread(player, "May not update chunk tracking for player async"); // Paper - replace chunk loader system - if (player.level == this.level) { -+ ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong()); // Paper - replace chunk loader system - move up - if (newWithinViewDistance && !oldWithinViewDistance) { -- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong()); -+ // Paper - replace chunk loader system - move up - - if (playerchunk != null) { - LevelChunk chunk = playerchunk.getSendingChunk(); // Paper - replace chunk loader system - - if (chunk != null) { -+ playerchunk.addPlayer(player); // Paper - replace chunk loader system - this.playerLoadedChunk(player, packet, chunk); - } - -@@ -848,10 +856,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - if (!newWithinViewDistance && oldWithinViewDistance) { -+ // Paper start - replace chunk loader system -+ if (playerchunk != null) { -+ playerchunk.removePlayer(player); -+ } else { -+ LOGGER.warn("ChunkHolder at " + pos + " in world '" + this.level.getWorld().getName() + "' does not exist to untrack chunk for " + player, new Throwable()); -+ } -+ // Paper end - replace chunk loader system - player.untrackChunk(pos); - } - -- } -+ } else { LOGGER.warn("Mismatch in world for chunk " + pos + " in world '" + this.level.getWorld().getName() + "' for player " + player, new Throwable()); } // Paper - replace chunk loader system - } - - public int size() { -@@ -1151,34 +1166,18 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // Paper - replaced by PlayerChunkLoader - - this.updateMaps(player); // Paper - distance maps -- this.playerChunkManager.updatePlayer(player); // Paper - respond to movement immediately - - } - - @Override - public List getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge) { - // Paper start - per player view distance -- // there can be potential desync with player's last mapped section and the view distance map, so use the -- // view distance map here. -- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = this.playerChunkManager.broadcastMap.getObjectsInRange(chunkPos); -- if (players == null) { -- return java.util.Collections.emptyList(); -- } -- -- List ret = new java.util.ArrayList<>(players.size()); -- -- Object[] backingSet = players.getBackingSet(); -- for (int i = 0, len = backingSet.length; i < len; ++i) { -- if (!(backingSet[i] instanceof ServerPlayer player)) { -- continue; -- } -- if (!this.playerChunkManager.isChunkSent(player, chunkPos.x, chunkPos.z, onlyOnWatchDistanceEdge)) { -- continue; -- } -- ret.add(player); -+ ChunkHolder holder = this.getVisibleChunkIfPresent(chunkPos.toLong()); -+ if (holder == null) { -+ return new java.util.ArrayList<>(); -+ } else { -+ return holder.getPlayers(onlyOnWatchDistanceEdge); - } -- -- return ret; - // Paper end - per player view distance - } - -@@ -1612,7 +1611,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - double vec3d_dx = player.getX() - this.entity.getX(); - double vec3d_dz = player.getZ() - this.entity.getZ(); - // Paper end - remove allocation of Vec3D here -- double d0 = (double) Math.min(this.getEffectiveRange(), io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player) * 16); // Paper - per player view distance -+ double d0 = (double) Math.min(this.getEffectiveRange(), io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player) * 16); // Paper - per player view distance - double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper - double d2 = d0 * d0; - boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player); -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index 52cba8f68d274cce106304aef1249a95474d3238..88fca8b160df6804f30ed2cf8cf1f645085434e2 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -184,17 +184,17 @@ public abstract class DistanceManager { - } - - protected void updatePlayerTickets(int viewDistance) { -- this.chunkMap.playerChunkManager.setTargetNoTickViewDistance(viewDistance); // Paper - route to player chunk manager -+ this.chunkMap.setViewDistance(viewDistance);// Paper - route to player chunk manager - } - - // Paper start - public int getSimulationDistance() { -- return this.chunkMap.playerChunkManager.getTargetTickViewDistance(); // Paper - route to player chunk manager -+ return this.chunkMap.level.playerChunkLoader.getAPITickDistance(); - } - // Paper end - - public void updateSimulationDistance(int simulationDistance) { -- this.chunkMap.playerChunkManager.setTargetTickViewDistance(simulationDistance); // Paper - route to player chunk manager -+ this.chunkMap.level.playerChunkLoader.setTickDistance(simulationDistance); // Paper - route to player chunk manager - } - - public int getNaturalSpawnChunkCount() { -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index ca84eddbdb1e198b899750e5f6b3eafd25ce970f..5c7b8041e96ede9e662dfdb5285539bf51304650 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -645,7 +645,7 @@ public class ServerChunkCache extends ChunkSource { - this.level.getProfiler().popPush("chunks"); - if (tickChunks) { - this.level.timings.chunks.startTiming(); // Paper - timings -- this.chunkMap.playerChunkManager.tick(); // Paper - this is mostly is to account for view distance changes -+ this.chunkMap.level.playerChunkLoader.tick(); // Paper - replace player chunk loader - this is mostly required to account for view distance changes - this.tickChunks(); - this.level.timings.chunks.stopTiming(); // Paper - timings - } -@@ -1001,7 +1001,7 @@ public class ServerChunkCache extends ChunkSource { - @Override - // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task - public boolean pollTask() { -- ServerChunkCache.this.chunkMap.playerChunkManager.tickMidTick(); -+ // Paper - replace player chunk loader - if (ServerChunkCache.this.runDistanceManagerUpdates()) { - return true; - } -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 45804711255f04110e9509df8d60900314aa10b7..7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -521,6 +521,48 @@ public class ServerLevel extends Level implements WorldGenLevel { - } - // Paper end - optimise get nearest players for entity AI - -+ public final io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader playerChunkLoader = new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader(this); -+ private final java.util.concurrent.atomic.AtomicReference viewDistances = new java.util.concurrent.atomic.AtomicReference<>(new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances(-1, -1, -1)); -+ -+ public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances getViewDistances() { -+ return this.viewDistances.get(); -+ } -+ -+ private void updateViewDistance(final java.util.function.Function update) { -+ for (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances curr = this.viewDistances.get();;) { -+ if (this.viewDistances.compareAndSet(curr, update.apply(curr))) { -+ return; -+ } -+ } -+ } -+ -+ public void setTickViewDistance(final int distance) { -+ if ((distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE)) { -+ throw new IllegalArgumentException("Tick view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE) + ", got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setTickViewDistance(distance); -+ }); -+ } -+ -+ public void setLoadViewDistance(final int distance) { -+ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Load view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setLoadViewDistance(distance); -+ }); -+ } -+ -+ public void setSendViewDistance(final int distance) { -+ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Send view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setSendViewDistance(distance); -+ }); -+ } -+ - // 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) { - // IRegistryCustom.Dimension iregistrycustom_dimension = minecraftserver.registryAccess(); // CraftBukkit - decompile error -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 98df2463bf41fc736aa6a2b6ddf89e5abde6eb39..37dfb5f2e56d8d7bf114dac6be82ccf0fbfc8a09 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -278,6 +278,48 @@ public class ServerPlayer extends Player { - public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - there are a lot of changes to do if we change all methods leading to the event - -+ private final java.util.concurrent.atomic.AtomicReference viewDistances = new java.util.concurrent.atomic.AtomicReference<>(new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances(-1, -1, -1)); -+ public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader; -+ -+ public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances getViewDistances() { -+ return this.viewDistances.get(); -+ } -+ -+ private void updateViewDistance(final java.util.function.Function update) { -+ for (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances curr = this.viewDistances.get();;) { -+ if (this.viewDistances.compareAndSet(curr, update.apply(curr))) { -+ return; -+ } -+ } -+ } -+ -+ public void setTickViewDistance(final int distance) { -+ if ((distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE)) { -+ throw new IllegalArgumentException("Tick view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE) + ", got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setTickViewDistance(distance); -+ }); -+ } -+ -+ public void setLoadViewDistance(final int distance) { -+ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Load view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setLoadViewDistance(distance); -+ }); -+ } -+ -+ public void setSendViewDistance(final int distance) { -+ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Send view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); -+ } -+ this.updateViewDistance((input) -> { -+ return input.setSendViewDistance(distance); -+ }); -+ } -+ - public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile) { - super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); - this.chatVisibility = ChatVisiblity.FULL; -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 92e758a286a5db079c32d53cc52c8a422457daef..7679fa8c905e6cee1906814a82f8ab4ab925b0fc 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -278,7 +278,7 @@ public abstract class PlayerList { - boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO); - - // Spigot - view distance -- playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), this.server.levelKeys(), this.synchronizedRegistries, worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), this.getMaxPlayers(), worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance(), worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance(), flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat(), player.getLastDeathLocation())); // Paper - replace old player chunk management -+ playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), this.server.levelKeys(), this.synchronizedRegistries, worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), this.getMaxPlayers(), worldserver1.getWorld().getSendViewDistance(), worldserver1.getWorld().getSimulationDistance(), flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat(), player.getLastDeathLocation())); // Paper - replace old player chunk management - player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit - playerconnection.send(new ClientboundUpdateEnabledFeaturesPacket(FeatureFlags.REGISTRY.toNames(worldserver1.enabledFeatures()))); - playerconnection.send(new ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.BRAND, (new FriendlyByteBuf(Unpooled.buffer())).writeUtf(this.getServer().getServerModName()))); -@@ -912,8 +912,8 @@ public abstract class PlayerList { - // CraftBukkit start - LevelData worlddata = worldserver1.getLevelData(); - entityplayer1.connection.send(new ClientboundRespawnPacket(worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), entityplayer1.gameMode.getGameModeForPlayer(), entityplayer1.gameMode.getPreviousGameModeForPlayer(), worldserver1.isDebug(), worldserver1.isFlat(), (byte) i, entityplayer1.getLastDeathLocation())); -- entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance())); // Spigot // Paper - replace old player chunk management -- entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance())); // Spigot // Paper - replace old player chunk management -+ entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getWorld().getSendViewDistance())); // Spigot // Paper - replace old player chunk management -+ entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.getWorld().getSimulationDistance())); // Spigot // Paper - replace old player chunk management - entityplayer1.spawnIn(worldserver1); - entityplayer1.unsetRemoved(); - entityplayer1.connection.teleport(CraftLocation.toBukkit(entityplayer1.position(), worldserver1.getWorld(), entityplayer1.getYRot(), entityplayer1.getXRot())); -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 973ecd50f9cb6b86c353586e84d15dcb118ccb60..944da18bcc993ab0488a34cbbe9df134c355301a 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -635,7 +635,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i); - // Paper start - per player view distance - allow block updates for non-ticking chunks in player view distance - // if copied from above -- } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || ((ServerLevel)this).getChunkSource().chunkMap.playerChunkManager.broadcastMap.getObjectsInRange(io.papermc.paper.util.MCUtil.getCoordinateKey(blockposition)) != null)) { // Paper - replace old player chunk management -+ } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0)) { // Paper - replace old player chunk management - ((ServerLevel)this).getChunkSource().blockChanged(blockposition); - // Paper end - per player view distance - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index d190bad5d287766ed4165ed827d9901a9d878687..6143607ad1485c552309ac3df37b3ba9adb7c07c 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -177,43 +177,6 @@ public class LevelChunk extends ChunkAccess { - - protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) { - -- // Paper start - no-tick view distance -- ServerChunkCache chunkProviderServer = ((ServerLevel)this.level).getChunkSource(); -- net.minecraft.server.level.ChunkMap chunkMap = chunkProviderServer.chunkMap; -- // this code handles the addition of ticking tickets - the distance map handles the removal -- if (!areNeighboursLoaded(bitsetBefore, 2) && areNeighboursLoaded(bitsetAfter, 2)) { -- if (chunkMap.playerChunkManager.tickMap.getObjectsInRange(this.coordinateKey) != null) { // Paper - replace old player chunk loading system -- // now we're ready for entity ticking -- chunkProviderServer.mainThreadProcessor.execute(() -> { -- // double check that this condition still holds. -- if (LevelChunk.this.areNeighboursLoaded(2) && chunkMap.playerChunkManager.tickMap.getObjectsInRange(LevelChunk.this.coordinateKey) != null) { // Paper - replace old player chunk loading system -- chunkMap.playerChunkManager.onChunkPlayerTickReady(this.chunkPos.x, this.chunkPos.z); // Paper - replace old player chunk -- chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.PLAYER, LevelChunk.this.chunkPos, 31, LevelChunk.this.chunkPos); // 31 -> entity ticking, TODO check on update -- } -- }); -- } -- } -- -- // this code handles the chunk sending -- if (!areNeighboursLoaded(bitsetBefore, 1) && areNeighboursLoaded(bitsetAfter, 1)) { -- // Paper start - replace old player chunk loading system -- if (chunkMap.playerChunkManager.isChunkNearPlayers(this.chunkPos.x, this.chunkPos.z)) { -- // the post processing is expensive, so we don't want to run it unless we're actually near -- // a player. -- chunkProviderServer.mainThreadProcessor.execute(() -> { -- if (!LevelChunk.this.areNeighboursLoaded(1)) { -- return; -- } -- LevelChunk.this.postProcessGeneration(); -- if (!LevelChunk.this.areNeighboursLoaded(1)) { -- return; -- } -- chunkMap.playerChunkManager.onChunkSendReady(this.chunkPos.x, this.chunkPos.z); -- }); -- } -- // Paper end - replace old player chunk loading system -- } -- // Paper end - no-tick view distance - } - - public final boolean isAnyNeighborsLoaded() { -@@ -899,7 +862,6 @@ public class LevelChunk extends ChunkAccess { - // Paper - rewrite chunk system - move into separate callback - org.bukkit.Server server = this.level.getCraftServer(); - // Paper - rewrite chunk system - move into separate callback -- ((ServerLevel)this.level).getChunkSource().chunkMap.playerChunkManager.onChunkLoad(this.chunkPos.x, this.chunkPos.z); // Paper - rewrite player chunk management - if (server != null) { - /* - * If it's a new world, the first few chunks are generated inside -@@ -1081,6 +1043,7 @@ public class LevelChunk extends ChunkAccess { - BlockState iblockdata1 = Block.updateFromNeighbourShapes(iblockdata, this.level, blockposition); - - this.level.setBlock(blockposition, iblockdata1, 20); -+ if (iblockdata1 != iblockdata) this.level.chunkSource.blockChanged(blockposition); // Paper - replace player chunk loader - notify since we send before processing full updates - } - } - -@@ -1100,7 +1063,6 @@ public class LevelChunk extends ChunkAccess { - this.upgradeData.upgrade(this); - } finally { // Paper start - replace chunk loader system - this.isPostProcessingDone = true; -- this.level.getChunkSource().chunkMap.playerChunkManager.onChunkPostProcessing(this.chunkPos.x, this.chunkPos.z); - } - // Paper end - replace chunk loader system - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 8f0234296397ca2d4a607dcea6093c6c606dc7d2..4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -2309,12 +2309,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - // Spigot start - @Override - public int getViewDistance() { -- return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance(); // Paper - replace old player chunk management -+ return this.getHandle().playerChunkLoader.getAPIViewDistance(); // Paper - replace player chunk loader - } - - @Override - public int getSimulationDistance() { -- return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance(); // Paper - replace old player chunk management -+ return this.getHandle().playerChunkLoader.getAPITickDistance(); // Paper - replace player chunk loader - } - // Spigot end - // Paper start - view distance api -@@ -2348,12 +2348,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - @Override - public int getSendViewDistance() { -- return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance(); -+ return this.getHandle().playerChunkLoader.getAPISendViewDistance(); // Paper - replace player chunk loader - } - - @Override - public void setSendViewDistance(int viewDistance) { -- getHandle().getChunkSource().chunkMap.playerChunkManager.setSendDistance(viewDistance); -+ this.getHandle().chunkSource.chunkMap.setSendViewDistance(viewDistance); // Paper - replace player chunk loader - } - // Paper end - view distance api - -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index be64633c8bcee96f2ad5247525cac965b7b031b1..e88d7a6b4835bdac1a247545b49e4161ade148cb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -195,44 +195,22 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - // Paper start - implement view distances - @Override - public int getViewDistance() { -- net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -- io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -- if (data == null) { -- return chunkMap.playerChunkManager.getTargetNoTickViewDistance(); -- } -- return data.getTargetNoTickViewDistance(); -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPIViewDistance(this); - } - - @Override - public void setViewDistance(int viewDistance) { -- net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -- io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -- if (data == null) { -- throw new IllegalStateException("Player is not attached to world"); -- } -- -- data.setTargetNoTickViewDistance(viewDistance); -+ this.getHandle().setLoadViewDistance(viewDistance < 0 ? viewDistance : viewDistance + 1); - } - - @Override - public int getSimulationDistance() { -- net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -- io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -- if (data == null) { -- return chunkMap.playerChunkManager.getTargetTickViewDistance(); -- } -- return data.getTargetTickViewDistance(); -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPITickViewDistance(this); - } - - @Override - public void setSimulationDistance(int simulationDistance) { -- net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -- io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -- if (data == null) { -- throw new IllegalStateException("Player is not attached to world"); -- } -- -- data.setTargetTickViewDistance(simulationDistance); -+ this.getHandle().setTickViewDistance(simulationDistance); - } - - @Override -@@ -247,23 +225,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public int getSendViewDistance() { -- net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -- io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -- if (data == null) { -- return chunkMap.playerChunkManager.getTargetSendDistance(); -- } -- return data.getTargetSendViewDistance(); -+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPISendViewDistance(this); - } - - @Override - public void setSendViewDistance(int viewDistance) { -- net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -- io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -- if (data == null) { -- throw new IllegalStateException("Player is not attached to world"); -- } -- -- data.setTargetSendViewDistance(viewDistance); -+ this.getHandle().setSendViewDistance(viewDistance); - } - // Paper end - implement view distances - diff --git a/patches/server/0005-Threaded-Regions.patch b/patches/server/0003-Threaded-Regions.patch similarity index 90% rename from patches/server/0005-Threaded-Regions.patch rename to patches/server/0003-Threaded-Regions.patch index 32a3b8f..f1c82c0 100644 --- a/patches/server/0005-Threaded-Regions.patch +++ b/patches/server/0003-Threaded-Regions.patch @@ -1728,23 +1728,6 @@ index 0000000000000000000000000000000000000000..ad8c590fe7479fcb3c7ff5dc3ac3a4d6 + this.incrementDirect(currentNode, endTime - lastStart); + } +} -diff --git a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java -index cab91880a08c6fdc545804911d295e0f24f4d983..8f2bff24256f0946f38e1d8b0ae9c8c921b0a2db 100644 ---- a/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java -+++ b/src/main/java/com/destroystokyo/paper/antixray/ChunkPacketBlockControllerAntiXray.java -@@ -179,11 +179,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo - return; - } - -- if (!Bukkit.isPrimaryThread()) { -- // Plugins? -- MinecraftServer.getServer().scheduleOnMain(() -> modifyBlocks(chunkPacket, chunkPacketInfo)); -- return; -- } -+ // Folia - region threading - - LevelChunk chunk = chunkPacketInfo.getChunk(); - int x = chunk.getPos().x; diff --git a/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java b/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java index 22a2547810d0c029f29685faddf7ac21cde2df0b..e36b4053eb2676e934b8c9c401bf58cfa7dd969c 100644 --- a/src/main/java/com/destroystokyo/paper/util/RedstoneWireTurbo.java @@ -1767,7 +1750,7 @@ index 22a2547810d0c029f29685faddf7ac21cde2df0b..e36b4053eb2676e934b8c9c401bf58cf // The variable 'k' holds the maximum redstone power value of any adjacent blocks. // If 'k' has the highest level of all neighbors, then the power level of this diff --git a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java -index 951a7df30bd70bb3051c04f592529d560be6948e..0761313364f96a2c1bdd6991a1d2ed9af6a19038 100644 +index 774fb97912f766589f3548f659618ad554e0503f..c44023a2f825507625a244ea8dfaa6073ed36806 100644 --- a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java +++ b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java @@ -98,7 +98,7 @@ public final class ChatProcessor { @@ -1848,10 +1831,10 @@ index 3c17001bcd3862a76a22df488bff80a0ff4d1b83..b2fffaa862df045bacb346f3cbe7eb96 } } diff --git a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java -index 61d03808c8d1ab822d9b2f31fab0de14089a3b15..370e649a255a456d7f901b22e26241e135009af7 100644 +index fccb8d7a99bef076838ebefa233f2f00a1364c30..e822f308315a955d00dcbedfc1b54d22b569c31e 100644 --- a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java +++ b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java -@@ -91,6 +91,9 @@ public final class ChunkSystem { +@@ -92,6 +92,9 @@ public final class ChunkSystem { for (int index = 0, len = chunkMap.regionManagers.size(); index < len; ++index) { chunkMap.regionManagers.get(index).addChunk(holder.pos.x, holder.pos.z); } @@ -1861,7 +1844,7 @@ index 61d03808c8d1ab822d9b2f31fab0de14089a3b15..370e649a255a456d7f901b22e26241e1 } public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) { -@@ -98,30 +101,34 @@ public final class ChunkSystem { +@@ -99,30 +102,34 @@ public final class ChunkSystem { for (int index = 0, len = chunkMap.regionManagers.size(); index < len; ++index) { chunkMap.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z); } @@ -1902,7 +1885,7 @@ index 61d03808c8d1ab822d9b2f31fab0de14089a3b15..370e649a255a456d7f901b22e26241e1 public static ChunkHolder getUnloadingChunkHolder(final ServerLevel level, final int chunkX, final int chunkZ) { diff --git a/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java -index 63c69c4da5fcbd5901c9fc3427f69626e16492ee..88c48279d04b2bc5a67de0fdbd2c266516cbcd49 100644 +index a642b02694eb50e21dee1c3dc0bc397c1712c71e..5002bd772473533da2177d609de33bf23161f1c5 100644 --- a/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java +++ b/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java @@ -234,7 +234,7 @@ public class RegionizedPlayerChunkLoader { @@ -1915,10 +1898,10 @@ index 63c69c4da5fcbd5901c9fc3427f69626e16492ee..88c48279d04b2bc5a67de0fdbd2c2665 if (loader == null || loader.world != this.world) { // not our problem anymore 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..515cc130a411f218ed20628eb918be9d770b9939 100644 +index 82ccaf612548a7dbab7e5aeffb6eb8db84367477..b9095f559472dd92375ea719886913f606f0374c 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 { +@@ -188,7 +188,12 @@ public final class EntityLookup implements LevelEntityGetter { @Override public Iterable getAll() { @@ -1932,7 +1915,7 @@ index 61c170555c8854b102c640b0b6a615f9f732edbf..515cc130a411f218ed20628eb918be9d } @Override -@@ -261,7 +266,9 @@ public final class EntityLookup implements LevelEntityGetter { +@@ -262,7 +267,9 @@ public final class EntityLookup implements LevelEntityGetter { if (newVisibility.ordinal() > oldVisibility.ordinal()) { // status upgrade if (!oldVisibility.isAccessible() && newVisibility.isAccessible()) { @@ -1942,7 +1925,7 @@ index 61c170555c8854b102c640b0b6a615f9f732edbf..515cc130a411f218ed20628eb918be9d EntityLookup.this.worldCallback.onTrackingStart(entity); } -@@ -275,7 +282,9 @@ public final class EntityLookup implements LevelEntityGetter { +@@ -276,7 +283,9 @@ public final class EntityLookup implements LevelEntityGetter { } if (oldVisibility.isAccessible() && !newVisibility.isAccessible()) { @@ -1952,7 +1935,7 @@ index 61c170555c8854b102c640b0b6a615f9f732edbf..515cc130a411f218ed20628eb918be9d EntityLookup.this.worldCallback.onTrackingEnd(entity); } } -@@ -385,11 +394,26 @@ public final class EntityLookup implements LevelEntityGetter { +@@ -386,11 +395,26 @@ public final class EntityLookup implements LevelEntityGetter { entity.setLevelCallback(new EntityCallback(entity)); @@ -1979,7 +1962,7 @@ index 61c170555c8854b102c640b0b6a615f9f732edbf..515cc130a411f218ed20628eb918be9d 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 { +@@ -408,6 +432,7 @@ public final class EntityLookup implements LevelEntityGetter { LOGGER.warn("Failed to remove entity " + entity + " from entity slices (" + sectionX + "," + sectionZ + ")"); } } @@ -1987,7 +1970,7 @@ index 61c170555c8854b102c640b0b6a615f9f732edbf..515cc130a411f218ed20628eb918be9d entity.sectionX = entity.sectionY = entity.sectionZ = Integer.MIN_VALUE; this.entityByLock.writeLock(); -@@ -823,6 +848,9 @@ public final class EntityLookup implements LevelEntityGetter { +@@ -824,6 +849,9 @@ public final class EntityLookup implements LevelEntityGetter { EntityLookup.this.entityStatusChange(entity, null, tickingState, Visibility.HIDDEN, false, false, reason.shouldDestroy()); this.entity.setLevelCallback(NoOpCallback.INSTANCE); @@ -1998,80 +1981,27 @@ index 61c170555c8854b102c640b0b6a615f9f732edbf..515cc130a411f218ed20628eb918be9d } 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 cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b04afa1e6 100644 +index abd0217cf0bff183c8e262edc173a53403797c1a..33524deb5e1eda5be53e5b426c88f5837eb3e512 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; - import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; - import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; - import ca.spottedleaf.concurrentutil.map.SWMRLong2ObjectHashTable; --import co.aikar.timings.Timing; - import com.google.common.collect.ImmutableList; - import com.google.gson.JsonArray; - import com.google.gson.JsonObject; -@@ -19,10 +18,12 @@ import it.unimi.dsi.fastutil.longs.Long2IntMap; - import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; - import it.unimi.dsi.fastutil.longs.Long2ObjectMap; - import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; - import it.unimi.dsi.fastutil.longs.LongArrayList; - import it.unimi.dsi.fastutil.longs.LongIterator; - import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet; - import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; - import net.minecraft.nbt.CompoundTag; - import io.papermc.paper.chunk.system.ChunkSystem; - import net.minecraft.server.MinecraftServer; -@@ -34,8 +35,6 @@ import net.minecraft.server.level.TicketType; - import net.minecraft.util.SortedArraySet; - import net.minecraft.util.Unit; - import net.minecraft.world.level.ChunkPos; --import net.minecraft.world.level.chunk.ChunkAccess; --import net.minecraft.world.level.chunk.ChunkStatus; - import org.bukkit.plugin.Plugin; - import org.slf4j.Logger; - import java.io.IOException; -@@ -54,6 +53,13 @@ import java.util.concurrent.locks.LockSupport; - import java.util.concurrent.locks.ReentrantLock; +@@ -53,6 +53,14 @@ import java.util.concurrent.atomic.AtomicReference; + import java.util.concurrent.locks.LockSupport; import java.util.function.Predicate; +// Folia start - region threading +import io.papermc.paper.threadedregions.RegionizedServer; +import io.papermc.paper.threadedregions.ThreadedRegionizer; +import io.papermc.paper.threadedregions.TickRegionScheduler; -+import io.papermc.paper.threadedregions.TickRegions; ++import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; ++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +// Folia end - region threading + public final class ChunkHolderManager { private static final Logger LOGGER = LogUtils.getClassLogger(); -@@ -63,40 +69,198 @@ public final class ChunkHolderManager { - public static final int ENTITY_TICKING_TICKET_LEVEL = 31; - public static final int MAX_TICKET_LEVEL = ChunkMap.MAX_CHUNK_DISTANCE; // inclusive - -- private static final long NO_TIMEOUT_MARKER = -1L; -+ // Folia start - region threading -+ private static final long NO_TIMEOUT_MARKER = Long.MIN_VALUE; -+ private static final long PROBE_MARKER = Long.MIN_VALUE + 1; -+ // special region threading fields -+ // this field contains chunk holders that were created in addTicketAtLevel -+ // because the chunk holders were created without a reliable unload hook (i.e creation for entity/poi loading, -+ // which always check for unload after their tasks finish) we need to do that ourselves later -+ private final ReferenceOpenHashSet specialCaseUnload = new ReferenceOpenHashSet<>(); -+ // Folia end - region threading - -- final ReentrantLock ticketLock = new ReentrantLock(); -+ public final ReentrantLock ticketLock = new ReentrantLock(); // Folia - region threading - - private final SWMRLong2ObjectHashTable chunkHolders = new SWMRLong2ObjectHashTable<>(16384, 0.25f); -- private final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap<>(8192, 0.25f); -- // what a disaster of a name -- // this is a map of removal tick to a map of chunks and the number of tickets a chunk has that are to expire that tick -- private final Long2ObjectOpenHashMap removeTickToChunkExpireTicketCount = new Long2ObjectOpenHashMap<>(); -+ // Folia - region threading - private final ServerLevel world; +@@ -112,27 +120,92 @@ public final class ChunkHolderManager { private final ChunkTaskScheduler taskScheduler; -- private long currentTick; + private long currentTick; - private final ArrayDeque pendingFullLoadUpdate = new ArrayDeque<>(); - private final ObjectRBTreeSet autoSaveQueue = new ObjectRBTreeSet<>((final NewChunkHolder c1, final NewChunkHolder c2) -> { @@ -2109,11 +2039,6 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b + + return Long.compare(coord1, coord2); + }); -+ private long currentTick; -+ private final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap<>(8192, 0.25f); -+ // what a disaster of a name -+ // this is a map of removal tick to a map of chunks and the number of tickets a chunk has that are to expire that tick -+ private final Long2ObjectOpenHashMap removeTickToChunkExpireTicketCount = new Long2ObjectOpenHashMap<>(); + + public void merge(final HolderManagerRegionData into, final long tickOffset) { + // Order doesn't really matter for the pending full update... @@ -2126,37 +2051,9 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b + holder.lastAutoSave += tickOffset; + into.autoSaveQueue.add(holder); + } -+ -+ final long chunkManagerTickOffset = into.currentTick - this.currentTick; -+ for (final Iterator>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final Long2ObjectMap.Entry>> entry = iterator.next(); -+ final SortedArraySet> oldTickets = entry.getValue(); -+ final SortedArraySet> newTickets = SortedArraySet.create(Math.max(4, oldTickets.size() + 1)); -+ for (final Ticket ticket : oldTickets) { -+ newTickets.add( -+ new Ticket(ticket.getType(), ticket.getTicketLevel(), ticket.key, -+ ticket.removalTick == NO_TIMEOUT_MARKER ? NO_TIMEOUT_MARKER : ticket.removalTick + chunkManagerTickOffset) -+ ); -+ } -+ into.tickets.put(entry.getLongKey(), newTickets); -+ } -+ for (final Iterator> iterator = this.removeTickToChunkExpireTicketCount.long2ObjectEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final Long2ObjectMap.Entry entry = iterator.next(); -+ into.removeTickToChunkExpireTicketCount.merge( -+ (long)(entry.getLongKey() + chunkManagerTickOffset), entry.getValue(), -+ (final Long2IntOpenHashMap t, final Long2IntOpenHashMap f) -> { -+ for (final Iterator itr = f.long2IntEntrySet().fastIterator(); itr.hasNext();) { -+ final Long2IntMap.Entry e = itr.next(); -+ t.addTo(e.getLongKey(), e.getIntValue()); -+ } -+ return t; -+ } -+ ); -+ } -+ } -+ + } + +- 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) { @@ -2172,82 +2069,22 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b + for (final NewChunkHolder autoSave : this.autoSaveQueue) { + final int regionCoordinateX = autoSave.chunkX >> chunkToRegionShift; + final int regionCoordinateZ = autoSave.chunkZ >> chunkToRegionShift; -+ + +- if (saveTickCompare != 0) { +- return saveTickCompare; + final HolderManagerRegionData data = regionToData.get(CoordinateUtils.getChunkKey(regionCoordinateX, regionCoordinateZ)); + if (data != null) { + data.autoSaveQueue.add(autoSave); + } // else: autoSave is an unloaded chunk holder + } -+ for (final HolderManagerRegionData data : dataSet) { -+ data.currentTick = this.currentTick; -+ } -+ for (final Iterator>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final Long2ObjectMap.Entry>> entry = iterator.next(); -+ final long chunkKey = entry.getLongKey(); -+ final int regionCoordinateX = CoordinateUtils.getChunkX(chunkKey) >> chunkToRegionShift; -+ final int regionCoordinateZ = CoordinateUtils.getChunkZ(chunkKey) >> chunkToRegionShift; -+ -+ // can never be null, since a chunk holder exists if the ticket set is not empty -+ regionToData.get(CoordinateUtils.getChunkKey(regionCoordinateX, regionCoordinateZ)).tickets.put(chunkKey, entry.getValue()); -+ } -+ for (final Iterator> iterator = this.removeTickToChunkExpireTicketCount.long2ObjectEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final Long2ObjectMap.Entry entry = iterator.next(); -+ final long tick = entry.getLongKey(); -+ final Long2IntOpenHashMap chunkToCount = entry.getValue(); -+ -+ for (final Iterator itr = chunkToCount.long2IntEntrySet().fastIterator(); itr.hasNext();) { -+ final Long2IntMap.Entry e = itr.next(); -+ final long chunkKey = e.getLongKey(); -+ final int regionCoordinateX = CoordinateUtils.getChunkX(chunkKey) >> chunkToRegionShift; -+ final int regionCoordinateZ = CoordinateUtils.getChunkZ(chunkKey) >> chunkToRegionShift; -+ final int count = e.getIntValue(); -+ -+ // can never be null, since a chunk holder exists if the ticket set is not empty -+ final HolderManagerRegionData data = regionToData.get(CoordinateUtils.getChunkKey(regionCoordinateX, regionCoordinateZ)); -+ -+ data.removeTickToChunkExpireTicketCount.computeIfAbsent(tick, (final long keyInMap) -> { -+ return new Long2IntOpenHashMap(); -+ }).put(chunkKey, count); -+ } -+ } -+ } -+ } -+ -+ private ChunkHolderManager.HolderManagerRegionData getCurrentRegionData() { -+ final ThreadedRegionizer.ThreadedRegion region = -+ TickRegionScheduler.getCurrentRegion(); -+ -+ if (region == null) { -+ return null; -+ } -+ -+ 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 int saveTickCompare = Long.compare(c1.lastAutoSave, c2.lastAutoSave); -+ return region.getData().getHolderManagerRegionData(); + } -+ -+ // MUST hold ticket lock -+ private ChunkHolderManager.HolderManagerRegionData getDataFor(final long key) { -+ return this.getDataFor(CoordinateUtils.getChunkX(key), CoordinateUtils.getChunkZ(key)); -+ } - -- if (saveTickCompare != 0) { -- return saveTickCompare; -+ // MUST hold ticket lock -+ private ChunkHolderManager.HolderManagerRegionData getDataFor(final int chunkX, final int chunkZ) { -+ if (!this.ticketLock.isHeldByCurrentThread()) { -+ throw new IllegalStateException("Must hold ticket level lock"); - } - final long coord1 = CoordinateUtils.getChunkKey(c1.chunkX, c1.chunkZ); - final long coord2 = CoordinateUtils.getChunkKey(c2.chunkX, c2.chunkZ); -+ final ThreadedRegionizer.ThreadedRegion region -+ = this.world.regioniser.getRegionAtUnsynchronised(chunkX, chunkZ); ++ private ChunkHolderManager.HolderManagerRegionData getCurrentRegionData() { ++ final ThreadedRegionizer.ThreadedRegion region = ++ TickRegionScheduler.getCurrentRegion(); - if (coord1 == coord2) { - throw new IllegalStateException("Duplicate chunkholder in auto save queue"); @@ -2257,6 +2094,10 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b - return Long.compare(coord1, coord2); - }); ++ 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()); ++ } ++ + return region.getData().getHolderManagerRegionData(); + } + // Folia end - region threading @@ -2264,7 +2105,7 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b public ChunkHolderManager(final ServerLevel world, final ChunkTaskScheduler taskScheduler) { this.world = world; -@@ -129,8 +293,13 @@ public final class ChunkHolderManager { +@@ -166,8 +239,13 @@ public final class ChunkHolderManager { } public void close(final boolean save, final boolean halt) { @@ -2279,7 +2120,7 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b LOGGER.info("Waiting 60s for chunk system to halt for world '" + this.world.getWorld().getName() + "'"); if (!this.taskScheduler.halt(true, TimeUnit.SECONDS.toNanos(60L))) { LOGGER.warn("Failed to halt world generation/loading tasks for world '" + this.world.getWorld().getName() + "'"); -@@ -140,9 +309,10 @@ public final class ChunkHolderManager { +@@ -177,9 +255,10 @@ public final class ChunkHolderManager { } if (save) { @@ -2291,7 +2132,7 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b if (this.world.chunkDataControllerNew.hasTasks() || this.world.entityDataControllerNew.hasTasks() || this.world.poiDataControllerNew.hasTasks()) { RegionFileIOThread.flush(); } -@@ -163,27 +333,34 @@ public final class ChunkHolderManager { +@@ -200,27 +279,34 @@ public final class ChunkHolderManager { } catch (final IOException ex) { LOGGER.error("Failed to close poi regionfile cache for world '" + this.world.getWorld().getName() + "'", ex); } @@ -2314,7 +2155,7 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b public void autoSave() { final List reschedule = new ArrayList<>(); - final long currentTick = MinecraftServer.currentTickLong; -+ final long currentTick = RegionizedServer.getCurrentTick(); ++ final long currentTick = RegionizedServer.getCurrentTick(); // Folia - region threading final long maxSaveTime = currentTick - this.world.paperConfig().chunks.autoSaveInterval.value(); - for (int autoSaved = 0; autoSaved < this.world.paperConfig().chunks.maxAutoSaveChunksPerTick && !this.autoSaveQueue.isEmpty();) { - final NewChunkHolder holder = this.autoSaveQueue.first(); @@ -2329,16 +2170,16 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b } - this.autoSaveQueue.remove(holder); -+ regionData.autoSaveQueue.remove(holder); ++ regionData.autoSaveQueue.remove(holder); // Folia - region threading holder.lastAutoSave = currentTick; if (holder.save(false, false) != null) { -@@ -197,15 +374,20 @@ public final class ChunkHolderManager { +@@ -234,15 +320,20 @@ public final class ChunkHolderManager { for (final NewChunkHolder holder : reschedule) { - if (holder.getChunkStatus().isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { + if (holder.getChunkStatus().isOrAfter(FullChunkStatus.FULL)) { - this.autoSaveQueue.add(holder); -+ regionData.autoSaveQueue.add(holder); ++ regionData.autoSaveQueue.add(holder); // Folia start - region threading } } } @@ -2356,7 +2197,7 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b LOGGER.info("Saving all chunkholders for world '" + this.world.getWorld().getName() + "'"); } -@@ -213,7 +395,7 @@ public final class ChunkHolderManager { +@@ -250,7 +341,7 @@ public final class ChunkHolderManager { int saved = 0; @@ -2365,7 +2206,7 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b long lastLog = start; boolean needsFlush = false; final int flushInterval = 50; -@@ -224,6 +406,12 @@ public final class ChunkHolderManager { +@@ -261,6 +352,12 @@ public final class ChunkHolderManager { for (int i = 0, len = holders.size(); i < len; ++i) { final NewChunkHolder holder = holders.get(i); @@ -2378,7 +2219,7 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b try { final NewChunkHolder.SaveStat saveStat = holder.save(shutdown, false); if (saveStat != null) { -@@ -256,7 +444,7 @@ public final class ChunkHolderManager { +@@ -293,7 +390,7 @@ public final class ChunkHolderManager { } } } @@ -2387,288 +2228,39 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b RegionFileIOThread.flush(); if (this.world.paperConfig().chunks.flushRegionsOnSave) { try { -@@ -297,18 +485,16 @@ public final class ChunkHolderManager { - } - - public boolean hasTickets() { -- this.ticketLock.lock(); -- try { -- return !this.tickets.isEmpty(); -- } finally { -- this.ticketLock.unlock(); -- } -+ return !this.getTicketsCopy().isEmpty(); // Folia - region threading - } - - public String getTicketDebugString(final long coordinate) { - this.ticketLock.lock(); - try { -- final SortedArraySet> tickets = this.tickets.get(coordinate); -+ // Folia start - region threading -+ final ChunkHolderManager.HolderManagerRegionData holderManagerRegionData = this.getDataFor(coordinate); -+ final SortedArraySet> tickets = holderManagerRegionData == null ? null : holderManagerRegionData.tickets.get(coordinate); -+ // Folia end - region threading - - return tickets != null ? tickets.first().toString() : "no_ticket"; - } finally { -@@ -319,7 +505,17 @@ public final class ChunkHolderManager { - public Long2ObjectOpenHashMap>> getTicketsCopy() { - this.ticketLock.lock(); - try { -- return this.tickets.clone(); -+ // Folia start - region threading -+ Long2ObjectOpenHashMap>> ret = new Long2ObjectOpenHashMap<>(); -+ this.world.regioniser.computeForAllRegions((region) -> { -+ for (final LongIterator iterator = region.getData().getHolderManagerRegionData().tickets.keySet().longIterator(); iterator.hasNext();) { -+ final long chunk = iterator.nextLong(); -+ -+ ret.put(chunk, region.getData().getHolderManagerRegionData().tickets.get(chunk)); -+ } -+ }); -+ return ret; -+ // Folia end - region threading - } finally { - this.ticketLock.unlock(); - } -@@ -329,7 +525,11 @@ public final class ChunkHolderManager { - ImmutableList.Builder ret; - this.ticketLock.lock(); - try { -- SortedArraySet> tickets = this.tickets.get(ChunkPos.asLong(x, z)); -+ // Folia start - region threading -+ final long coordinate = CoordinateUtils.getChunkKey(x, z); -+ final ChunkHolderManager.HolderManagerRegionData holderManagerRegionData = this.getDataFor(coordinate); -+ final SortedArraySet> tickets = holderManagerRegionData == null ? null : holderManagerRegionData.tickets.get(coordinate); -+ // Folia end - region threading - - if (tickets == null) { - return Collections.emptyList(); -@@ -382,12 +582,37 @@ public final class ChunkHolderManager { - return false; - } - -+ // Folia start - region threading -+ final ThreadedRegionizer.ThreadedRegion currRegion = TickRegionScheduler.getCurrentRegion(); -+ final boolean lock = currRegion == null || this.world.regioniser.getRegionAtUnsynchronised( -+ CoordinateUtils.getChunkX(chunk), CoordinateUtils.getChunkZ(chunk) -+ ) != currRegion; -+ // Folia end - region threading -+ - this.ticketLock.lock(); - try { -- final long removeTick = removeDelay == 0 ? NO_TIMEOUT_MARKER : this.currentTick + removeDelay; -+ // Folia start - region threading -+ NewChunkHolder holder = this.chunkHolders.get(chunk); -+ if (holder == null) { -+ // we need to guarantee that a chunk holder exists for each ticket -+ // this must be executed before retrieving the holder manager data for a target chunk, to ensure the -+ // region will exist -+ this.chunkHolders.put(chunk, holder = this.createChunkHolder(chunk)); -+ this.specialCaseUnload.add(holder); -+ } -+ -+ if (lock) { -+ // we just need to prevent merging, so we only need the read lock -+ // additionally, this will prevent deadlock in the remove all tickets function by using the read lock -+ this.world.regioniser.acquireReadLock(); -+ } -+ try { -+ final ChunkHolderManager.HolderManagerRegionData targetData = lock ? this.getDataFor(chunk) : currRegion.getData().getHolderManagerRegionData(); -+ // Folia end - region threading -+ final long removeTick = removeDelay == 0 ? NO_TIMEOUT_MARKER : targetData.currentTick + removeDelay; // Folia - region threading - final Ticket ticket = new Ticket<>(type, level, identifier, removeTick); - -- final SortedArraySet> ticketsAtChunk = this.tickets.computeIfAbsent(chunk, (final long keyInMap) -> { -+ final SortedArraySet> ticketsAtChunk = targetData.tickets.computeIfAbsent(chunk, (final long keyInMap) -> { // Folia - region threading - return SortedArraySet.create(4); - }); - -@@ -399,25 +624,25 @@ public final class ChunkHolderManager { - final long oldRemovalTick = current.removalTick; - if (removeTick != oldRemovalTick) { - if (oldRemovalTick != NO_TIMEOUT_MARKER) { -- final Long2IntOpenHashMap removeCounts = this.removeTickToChunkExpireTicketCount.get(oldRemovalTick); -+ final Long2IntOpenHashMap removeCounts = targetData.removeTickToChunkExpireTicketCount.get(oldRemovalTick); // Folia - region threading - final int prevCount = removeCounts.addTo(chunk, -1); - - if (prevCount == 1) { - removeCounts.remove(chunk); - if (removeCounts.isEmpty()) { -- this.removeTickToChunkExpireTicketCount.remove(oldRemovalTick); -+ targetData.removeTickToChunkExpireTicketCount.remove(oldRemovalTick); // Folia - region threading - } - } - } - if (removeTick != NO_TIMEOUT_MARKER) { -- this.removeTickToChunkExpireTicketCount.computeIfAbsent(removeTick, (final long keyInMap) -> { -+ targetData.removeTickToChunkExpireTicketCount.computeIfAbsent(removeTick, (final long keyInMap) -> { // Folia - region threading - return new Long2IntOpenHashMap(); - }).addTo(chunk, 1); - } - } - } else { - if (removeTick != NO_TIMEOUT_MARKER) { -- this.removeTickToChunkExpireTicketCount.computeIfAbsent(removeTick, (final long keyInMap) -> { -+ targetData.removeTickToChunkExpireTicketCount.computeIfAbsent(removeTick, (final long keyInMap) -> { // Folia - region threading - return new Long2IntOpenHashMap(); - }).addTo(chunk, 1); - } -@@ -428,6 +653,11 @@ public final class ChunkHolderManager { - } - - return current == ticket; -+ } finally { // Folia start - region threading -+ if (lock) { -+ this.world.regioniser.releaseReadLock(); -+ } -+ } // Folia end - region threading - } finally { - this.ticketLock.unlock(); - } -@@ -446,35 +676,70 @@ public final class ChunkHolderManager { - return false; - } - -+ // Folia start - region threading -+ final ThreadedRegionizer.ThreadedRegion currRegion = TickRegionScheduler.getCurrentRegion(); -+ final boolean lock = currRegion == null || this.world.regioniser.getRegionAtUnsynchronised( -+ CoordinateUtils.getChunkX(chunk), CoordinateUtils.getChunkZ(chunk) -+ ) != currRegion; -+ // Folia end - region threading -+ - this.ticketLock.lock(); - try { -- final SortedArraySet> ticketsAtChunk = this.tickets.get(chunk); -+ // Folia start - region threading -+ if (lock) { -+ // we just need to prevent merging, so we only need the read lock -+ // additionally, this will prevent deadlock in the remove all tickets function by using the read lock -+ this.world.regioniser.acquireReadLock(); -+ } -+ try { -+ final ChunkHolderManager.HolderManagerRegionData targetData = lock ? this.getDataFor(chunk) : currRegion.getData().getHolderManagerRegionData(); -+ // Folia end - region threading -+ -+ final SortedArraySet> ticketsAtChunk = targetData == null ? null : targetData.tickets.get(chunk); -+ // Folia end - region threading - if (ticketsAtChunk == null) { - return false; - } - - final int oldLevel = getTicketLevelAt(ticketsAtChunk); -- final Ticket ticket = (Ticket)ticketsAtChunk.removeAndGet(new Ticket<>(type, level, identifier, -2L)); -+ final Ticket ticket = (Ticket)ticketsAtChunk.removeAndGet(new Ticket<>(type, level, identifier, PROBE_MARKER)); // Folia - region threading - - if (ticket == null) { - return false; - } - -+ int newLevel = getTicketLevelAt(ticketsAtChunk); // Folia - region threading - moved up from below -+ // Folia start - region threading -+ // we should not change the ticket levels while the target region may be ticking -+ if (newLevel > level) { -+ final long unknownRemoveTick = targetData.currentTick + Math.max(0, TicketType.UNKNOWN.timeout); -+ final Ticket unknownTicket = new Ticket<>(TicketType.UNKNOWN, level, new ChunkPos(chunk), unknownRemoveTick); -+ if (ticketsAtChunk.add(unknownTicket)) { -+ targetData.removeTickToChunkExpireTicketCount.computeIfAbsent(unknownRemoveTick, (final long keyInMap) -> { -+ return new Long2IntOpenHashMap(); -+ }).addTo(chunk, 1); -+ } else { -+ throw new IllegalStateException("Should have been able to add " + unknownTicket + " to " + ticketsAtChunk); -+ } -+ newLevel = level; -+ } -+ // Folia end - region threading -+ - if (ticketsAtChunk.isEmpty()) { -- this.tickets.remove(chunk); -+ targetData.tickets.remove(chunk); // Folia - region threading - } - -- final int newLevel = getTicketLevelAt(ticketsAtChunk); -+ // Folia - region threading - move up - - final long removeTick = ticket.removalTick; - if (removeTick != NO_TIMEOUT_MARKER) { -- final Long2IntOpenHashMap removeCounts = this.removeTickToChunkExpireTicketCount.get(removeTick); -+ final Long2IntOpenHashMap removeCounts = targetData.removeTickToChunkExpireTicketCount.get(removeTick); // Folia - region threading - final int currCount = removeCounts.addTo(chunk, -1); - - if (currCount == 1) { - removeCounts.remove(chunk); - if (removeCounts.isEmpty()) { -- this.removeTickToChunkExpireTicketCount.remove(removeTick); -+ targetData.removeTickToChunkExpireTicketCount.remove(removeTick); // Folia - region threading - } - } - } -@@ -484,6 +749,11 @@ public final class ChunkHolderManager { - } - - return true; -+ } finally { // Folia start - region threading -+ if (lock) { -+ this.world.regioniser.releaseReadLock(); -+ } -+ } // Folia end - region threading - } finally { - this.ticketLock.unlock(); - } -@@ -523,24 +793,33 @@ public final class ChunkHolderManager { - - this.ticketLock.lock(); - try { -- for (final LongIterator iterator = new LongArrayList(this.tickets.keySet()).longIterator(); iterator.hasNext();) { -- final long chunk = iterator.nextLong(); -+ // Folia start - region threading -+ this.world.regioniser.computeForAllRegions((region) -> { -+ for (final LongIterator iterator = new LongArrayList(region.getData().getHolderManagerRegionData().tickets.keySet()).longIterator(); iterator.hasNext();) { -+ final long chunk = iterator.nextLong(); - -- this.removeTicketAtLevel(ticketType, chunk, ticketLevel, ticketIdentifier); -- } -+ this.removeTicketAtLevel(ticketType, chunk, ticketLevel, ticketIdentifier); -+ } -+ }); -+ // Folia end - region threading - } finally { - this.ticketLock.unlock(); - } +@@ -706,6 +803,13 @@ public final class ChunkHolderManager { } public void tick() { -- TickThread.ensureTickThread("Cannot tick ticket manager off-main"); + // Folia start - region threading -+ final ChunkHolderManager.HolderManagerRegionData data = this.getCurrentRegionData(); -+ if (data == null) { ++ final ThreadedRegionizer.ThreadedRegion region = ++ TickRegionScheduler.getCurrentRegion(); ++ if (region == null) { + throw new IllegalStateException("Not running tick() while on a region"); + } + // Folia end - region threading + final int sectionShift = TickRegions.getRegionChunkShift(); - this.ticketLock.lock(); - try { -- final long tick = ++this.currentTick; -+ final long tick = ++data.currentTick; // Folia - region threading + final Predicate> expireNow = (final Ticket ticket) -> { +@@ -715,12 +819,12 @@ public final class ChunkHolderManager { + return --ticket.removeDelay <= 0L; + }; -- final Long2IntOpenHashMap toRemove = this.removeTickToChunkExpireTicketCount.remove(tick); -+ final Long2IntOpenHashMap toRemove = data.removeTickToChunkExpireTicketCount.remove(tick); // Folia - region threading +- for (final Iterator iterator = this.sectionToChunkToExpireCount.keySet().iterator(); iterator.hasNext();) { +- final RegionFileIOThread.ChunkCoordinate section = iterator.next(); +- final long sectionKey = section.key; +- ++ // Folia start - region threading ++ for (final LongIterator iterator = region.getOwnedSectionsUnsynchronised(); iterator.hasNext();) { ++ final long sectionKey = iterator.nextLong(); ++ final RegionFileIOThread.ChunkCoordinate section = new RegionFileIOThread.ChunkCoordinate(sectionKey); + if (!this.sectionToChunkToExpireCount.containsKey(section)) { +- // removed concurrently ++ // Folia end - region threading + continue; + } - if (toRemove == null) { - return; -@@ -553,10 +832,10 @@ public final class ChunkHolderManager { - for (final LongIterator iterator = toRemove.keySet().longIterator(); iterator.hasNext();) { - final long chunk = iterator.nextLong(); - -- final SortedArraySet> tickets = this.tickets.get(chunk); -+ final SortedArraySet> tickets = data.tickets.get(chunk); // Folia - region threading - tickets.removeIf(expireNow); - if (tickets.isEmpty()) { -- this.tickets.remove(chunk); -+ data.tickets.remove(chunk); // Folia - region threading - this.ticketLevelPropagator.removeSource(chunk); - } else { - this.ticketLevelPropagator.setSource(chunk, convertBetweenTicketLevels(tickets.first().getTicketLevel())); -@@ -805,30 +1084,62 @@ public final class ChunkHolderManager { +@@ -1023,19 +1127,51 @@ public final class ChunkHolderManager { if (changedFullStatus.isEmpty()) { return; } @@ -2685,6 +2277,7 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b - final ArrayDeque pendingFullLoadUpdate = this.pendingFullLoadUpdate; - for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { - pendingFullLoadUpdate.add(changedFullStatus.get(i)); ++ // Folia start - region threading + final Long2ObjectOpenHashMap> sectionToUpdates = new Long2ObjectOpenHashMap<>(); + final List thisRegionHolders = new ArrayList<>(); + @@ -2706,7 +2299,9 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b + }).add(holder); + } + } ++ // Folia end - region threading + ++ // Folia start - region threading + if (!thisRegionHolders.isEmpty()) { + thisRegion.getData().getHolderManagerRegionData().pendingFullLoadUpdate.addAll(thisRegionHolders); + } @@ -2725,15 +2320,11 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b + ChunkHolderManager.this.getCurrentRegionData().pendingFullLoadUpdate.addAll(regionHolders); + ChunkHolderManager.this.processPendingFullUpdate(); + }, PrioritisedExecutor.Priority.HIGHEST); ++ // Folia end - region threading } } } - - final ReferenceLinkedOpenHashSet unloadQueue = new ReferenceLinkedOpenHashSet<>(); - -+ /* -+ * Note: Only called on chunk holders that the current ticking region owns -+ */ +@@ -1043,8 +1179,9 @@ public final class ChunkHolderManager { private void removeChunkHolder(final NewChunkHolder holder) { holder.killed = true; holder.vanillaChunkHolder.onChunkRemove(); @@ -2741,91 +2332,36 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b + // Folia - region threading ChunkSystem.onChunkHolderDelete(this.world, holder.vanillaChunkHolder); + this.getCurrentRegionData().autoSaveQueue.remove(holder); // Folia - region threading - this.chunkHolders.remove(CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ)); - } - -@@ -846,6 +1157,8 @@ public final class ChunkHolderManager { - throw new IllegalStateException("Cannot hold scheduling lock while calling processUnloads"); + synchronized (this.chunkHolders) { + this.chunkHolders.remove(CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ)); } - -+ final ChunkHolderManager.HolderManagerRegionData currentData = this.getCurrentRegionData(); // Folia - region threading -+ - final List unloadQueue; - final List scheduleList = new ArrayList<>(); - this.ticketLock.lock(); -@@ -858,11 +1171,22 @@ public final class ChunkHolderManager { - // in order to ensure all chunks in the unload queue do not have a pending ticket level update, - // process them now - this.processTicketUpdates(false, false, scheduleList); -- unloadQueue = new ArrayList<>((int)(this.unloadQueue.size() * 0.05) + 1); - -- final int unloadCount = Math.max(50, (int)(this.unloadQueue.size() * 0.05)); -- for (int i = 0; i < unloadCount && !this.unloadQueue.isEmpty(); ++i) { -- final NewChunkHolder chunkHolder = this.unloadQueue.removeFirst(); -+ // Folia start - region threading -+ final ArrayDeque toUnload = new ArrayDeque<>(); -+ // The unload queue is globally maintained, but we can only unload chunks in our region -+ for (final NewChunkHolder holder : this.unloadQueue) { -+ if (TickThread.isTickThreadFor(this.world, holder.chunkX, holder.chunkZ)) { -+ toUnload.add(holder); -+ } -+ } -+ // Folia end - region threading -+ -+ final int unloadCount = Math.max(50, (int)(toUnload.size() * 0.05)); // Folia - region threading -+ unloadQueue = new ArrayList<>(unloadCount + 1); // Folia - region threading -+ for (int i = 0; i < unloadCount && !toUnload.isEmpty(); ++i) { // Folia - region threading -+ final NewChunkHolder chunkHolder = toUnload.removeFirst(); // Folia - region threading -+ this.unloadQueue.remove(chunkHolder); // Folia - region threading - if (chunkHolder.isSafeToUnload() != null) { - LOGGER.error("Chunkholder " + chunkHolder + " is not safe to unload but is inside the unload queue?"); - continue; -@@ -1177,6 +1501,19 @@ public final class ChunkHolderManager { - this.ticketLevelUpdates.clear(); - } - } -+ -+ // Folia start - region threading -+ // it is possible that a special case new chunk holder had its ticket removed before it was propagated, -+ // which means checkUnload was never invoked. By checking unload here, we ensure that either the -+ // ticket level was propagated (in which case, a later depropagation would check again) or that -+ // we called checkUnload for it. -+ if (!this.specialCaseUnload.isEmpty()) { -+ for (final NewChunkHolder special : this.specialCaseUnload) { -+ special.checkUnload(); -+ } -+ this.specialCaseUnload.clear(); -+ } -+ // Folia end - region threading - } finally { - this.ticketLock.unlock(); - } -@@ -1200,7 +1537,12 @@ public final class ChunkHolderManager { +@@ -1371,7 +1508,13 @@ public final class ChunkHolderManager { // only call on tick thread protected final boolean processPendingFullUpdate() { - final ArrayDeque pendingFullLoadUpdate = this.pendingFullLoadUpdate; ++ // Folia start - region threading + final HolderManagerRegionData data = this.getCurrentRegionData(); + if (data == null) { + return false; + } -+ + final ArrayDeque pendingFullLoadUpdate = data.pendingFullLoadUpdate; ++ // Folia end - region threading boolean ret = false; -@@ -1211,9 +1553,7 @@ public final class ChunkHolderManager { +@@ -1382,9 +1525,7 @@ public final class ChunkHolderManager { ret |= holder.handleFullStatusChange(changedFullStatus); if (!changedFullStatus.isEmpty()) { - for (int i = 0, len = changedFullStatus.size(); i < len; ++i) { - pendingFullLoadUpdate.add(changedFullStatus.get(i)); - } -+ this.addChangedStatuses(changedFullStatus); ++ this.addChangedStatuses(changedFullStatus); // Folia - region threading changedFullStatus.clear(); } } -@@ -1263,7 +1603,7 @@ public final class ChunkHolderManager { +@@ -1398,7 +1539,7 @@ public final class ChunkHolderManager { private JsonObject getDebugJsonNoLock() { final JsonObject ret = new JsonObject(); @@ -2834,132 +2370,20 @@ index cb12c31ffe014e17eb9f901ab0a273802e3e0245..1e5df2593b21b8ee7636f5df28541f9b final JsonArray unloadQueue = new JsonArray(); ret.add("unload_queue", unloadQueue); -@@ -1282,60 +1622,73 @@ public final class ChunkHolderManager { - holders.add(holder.getDebugJson()); - } - -- final JsonArray removeTickToChunkExpireTicketCount = new JsonArray(); -- ret.add("remove_tick_to_chunk_expire_ticket_count", removeTickToChunkExpireTicketCount); -+ // Folia start - region threading -+ final JsonArray regions = new JsonArray(); -+ ret.add("regions", regions); -+ this.world.regioniser.computeForAllRegionsUnsynchronised((region) -> { -+ final JsonObject regionJson = new JsonObject(); -+ regions.add(regionJson); -+ -+ final TickRegions.TickRegionData regionData = region.getData(); - -- for (final Long2ObjectMap.Entry tickEntry : this.removeTickToChunkExpireTicketCount.long2ObjectEntrySet()) { -- final long tick = tickEntry.getLongKey(); -- final Long2IntOpenHashMap coordinateToCount = tickEntry.getValue(); -+ regionJson.addProperty("current_tick", Long.valueOf(regionData.getCurrentTick())); - -- final JsonObject tickJson = new JsonObject(); -- removeTickToChunkExpireTicketCount.add(tickJson); -+ final JsonArray removeTickToChunkExpireTicketCount = new JsonArray(); -+ regionJson.add("remove_tick_to_chunk_expire_ticket_count", removeTickToChunkExpireTicketCount); - -- tickJson.addProperty("tick", Long.valueOf(tick)); -+ for (final Long2ObjectMap.Entry tickEntry : regionData.getHolderManagerRegionData().removeTickToChunkExpireTicketCount.long2ObjectEntrySet()) { -+ final long tick = tickEntry.getLongKey(); -+ final Long2IntOpenHashMap coordinateToCount = tickEntry.getValue(); - -- final JsonArray tickEntries = new JsonArray(); -- tickJson.add("entries", tickEntries); -+ final JsonObject tickJson = new JsonObject(); -+ removeTickToChunkExpireTicketCount.add(tickJson); - -- for (final Long2IntMap.Entry entry : coordinateToCount.long2IntEntrySet()) { -- final long coordinate = entry.getLongKey(); -- final int count = entry.getIntValue(); -+ tickJson.addProperty("tick", Long.valueOf(tick)); - -- final JsonObject entryJson = new JsonObject(); -- tickEntries.add(entryJson); -+ final JsonArray tickEntries = new JsonArray(); -+ tickJson.add("entries", tickEntries); - -- entryJson.addProperty("chunkX", Long.valueOf(CoordinateUtils.getChunkX(coordinate))); -- entryJson.addProperty("chunkZ", Long.valueOf(CoordinateUtils.getChunkZ(coordinate))); -- entryJson.addProperty("count", Integer.valueOf(count)); -+ for (final Long2IntMap.Entry entry : coordinateToCount.long2IntEntrySet()) { -+ final long coordinate = entry.getLongKey(); -+ final int count = entry.getIntValue(); -+ -+ final JsonObject entryJson = new JsonObject(); -+ tickEntries.add(entryJson); -+ -+ entryJson.addProperty("chunkX", Long.valueOf(CoordinateUtils.getChunkX(coordinate))); -+ entryJson.addProperty("chunkZ", Long.valueOf(CoordinateUtils.getChunkZ(coordinate))); -+ entryJson.addProperty("count", Integer.valueOf(count)); -+ } - } -- } - -- final JsonArray allTicketsJson = new JsonArray(); -- ret.add("tickets", allTicketsJson); -+ final JsonArray allTicketsJson = new JsonArray(); -+ regionJson.add("tickets", allTicketsJson); - -- for (final Long2ObjectMap.Entry>> coordinateTickets : this.tickets.long2ObjectEntrySet()) { -- final long coordinate = coordinateTickets.getLongKey(); -- final SortedArraySet> tickets = coordinateTickets.getValue(); -+ for (final Long2ObjectMap.Entry>> coordinateTickets : regionData.getHolderManagerRegionData().tickets.long2ObjectEntrySet()) { -+ final long coordinate = coordinateTickets.getLongKey(); -+ final SortedArraySet> tickets = coordinateTickets.getValue(); - -- final JsonObject coordinateJson = new JsonObject(); -- allTicketsJson.add(coordinateJson); -+ final JsonObject coordinateJson = new JsonObject(); -+ allTicketsJson.add(coordinateJson); - -- coordinateJson.addProperty("chunkX", Long.valueOf(CoordinateUtils.getChunkX(coordinate))); -- coordinateJson.addProperty("chunkZ", Long.valueOf(CoordinateUtils.getChunkZ(coordinate))); -+ coordinateJson.addProperty("chunkX", Long.valueOf(CoordinateUtils.getChunkX(coordinate))); -+ coordinateJson.addProperty("chunkZ", Long.valueOf(CoordinateUtils.getChunkZ(coordinate))); - -- final JsonArray ticketsSerialized = new JsonArray(); -- coordinateJson.add("tickets", ticketsSerialized); -+ final JsonArray ticketsSerialized = new JsonArray(); -+ coordinateJson.add("tickets", ticketsSerialized); - -- for (final Ticket ticket : tickets) { -- final JsonObject ticketSerialized = new JsonObject(); -- ticketsSerialized.add(ticketSerialized); -+ for (final Ticket ticket : tickets) { -+ final JsonObject ticketSerialized = new JsonObject(); -+ ticketsSerialized.add(ticketSerialized); - -- ticketSerialized.addProperty("type", ticket.getType().toString()); -- ticketSerialized.addProperty("level", Integer.valueOf(ticket.getTicketLevel())); -- ticketSerialized.addProperty("identifier", Objects.toString(ticket.key)); -- ticketSerialized.addProperty("remove_tick", Long.valueOf(ticket.removalTick)); -+ ticketSerialized.addProperty("type", ticket.getType().toString()); -+ ticketSerialized.addProperty("level", Integer.valueOf(ticket.getTicketLevel())); -+ ticketSerialized.addProperty("identifier", Objects.toString(ticket.key)); -+ ticketSerialized.addProperty("remove_tick", Long.valueOf(ticket.removalTick)); -+ } - } -- } -+ }); -+ // Folia end - region threading - - return ret; - } diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java -index e944bd32df98a1dd7dde8c5ce3698b6d81862cbf..c185a85e18932ac8ee96a01779e1e1a580197613 100644 +index f975cb93716e137d973ff2f9011acdbef58859a2..8a5e3138c0f5e3f275c7352faf07bf39c04d00ca 100644 --- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java +++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java -@@ -117,7 +117,7 @@ public final class ChunkTaskScheduler { - public final PrioritisedThreadPool.PrioritisedPoolExecutor parallelGenExecutor; +@@ -114,7 +114,7 @@ public final class ChunkTaskScheduler { + private final PrioritisedThreadPool.PrioritisedPoolExecutor radiusAwareGenExecutor; public final PrioritisedThreadPool.PrioritisedPoolExecutor loadExecutor; - private final PrioritisedThreadedTaskQueue mainThreadExecutor = new PrioritisedThreadedTaskQueue(); + // Folia - regionised ticking - final ReentrantLock schedulingLock = new ReentrantLock(); public final ChunkHolderManager chunkHolderManager; -@@ -244,14 +244,13 @@ public final class ChunkTaskScheduler { + +@@ -300,14 +300,13 @@ public final class ChunkTaskScheduler { }; // this may not be good enough, specifically thanks to stupid ass plugins swallowing exceptions @@ -2976,8 +2400,8 @@ index e944bd32df98a1dd7dde8c5ce3698b6d81862cbf..c185a85e18932ac8ee96a01779e1e1a5 } public void raisePriority(final int x, final int z, final PrioritisedExecutor.Priority priority) { -@@ -271,7 +270,7 @@ public final class ChunkTaskScheduler { - public void scheduleTickingState(final int chunkX, final int chunkZ, final ChunkHolder.FullChunkStatus toStatus, +@@ -327,7 +326,7 @@ public final class ChunkTaskScheduler { + public void scheduleTickingState(final int chunkX, final int chunkZ, final FullChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer onComplete) { - if (!TickThread.isTickThread()) { @@ -2985,51 +2409,8 @@ index e944bd32df98a1dd7dde8c5ce3698b6d81862cbf..c185a85e18932ac8ee96a01779e1e1a5 this.scheduleChunkTask(chunkX, chunkZ, () -> { ChunkTaskScheduler.this.scheduleTickingState(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); }, priority); -@@ -384,9 +383,50 @@ public final class ChunkTaskScheduler { - }); - } +@@ -483,7 +482,7 @@ public final class ChunkTaskScheduler { -+ // Folia start - region threading -+ // only appropriate to use with ServerLevel#syncLoadNonFull -+ public boolean beginChunkLoadForNonFullSync(final int chunkX, final int chunkZ, final ChunkStatus toStatus, -+ final PrioritisedExecutor.Priority priority) { -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ final int minLevel = 33 + ChunkStatus.getDistance(toStatus); -+ final List tasks = new ArrayList<>(); -+ this.chunkHolderManager.ticketLock.lock(); -+ try { -+ this.schedulingLock.lock(); -+ try { -+ final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkKey); -+ if (chunkHolder == null || chunkHolder.getTicketLevel() > minLevel) { -+ return false; -+ } else { -+ final ChunkStatus genStatus = chunkHolder.getCurrentGenStatus(); -+ if (genStatus != null && genStatus.isOrAfter(toStatus)) { -+ return true; -+ } else { -+ chunkHolder.raisePriority(priority); -+ -+ if (!chunkHolder.upgradeGenTarget(toStatus)) { -+ this.schedule(chunkX, chunkZ, toStatus, chunkHolder, tasks); -+ } -+ } -+ } -+ } finally { -+ this.schedulingLock.unlock(); -+ } -+ } finally { -+ this.chunkHolderManager.ticketLock.unlock(); -+ } -+ -+ for (int i = 0, len = tasks.size(); i < len; ++i) { -+ tasks.get(i).schedule(); -+ } -+ -+ return true; -+ } -+ // Folia end - region threading -+ public void scheduleChunkLoad(final int chunkX, final int chunkZ, final ChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer onComplete) { - if (!TickThread.isTickThread()) { @@ -3037,7 +2418,7 @@ index e944bd32df98a1dd7dde8c5ce3698b6d81862cbf..c185a85e18932ac8ee96a01779e1e1a5 this.scheduleChunkTask(chunkX, chunkZ, () -> { ChunkTaskScheduler.this.scheduleChunkLoad(chunkX, chunkZ, toStatus, addTicket, priority, onComplete); }, priority); -@@ -413,7 +453,7 @@ public final class ChunkTaskScheduler { +@@ -511,7 +510,7 @@ public final class ChunkTaskScheduler { this.chunkHolderManager.processTicketUpdates(); } @@ -3046,7 +2427,7 @@ index e944bd32df98a1dd7dde8c5ce3698b6d81862cbf..c185a85e18932ac8ee96a01779e1e1a5 try { if (onComplete != null) { onComplete.accept(chunk); -@@ -453,7 +493,9 @@ public final class ChunkTaskScheduler { +@@ -551,7 +550,9 @@ public final class ChunkTaskScheduler { if (!chunkHolder.upgradeGenTarget(toStatus)) { this.schedule(chunkX, chunkZ, toStatus, chunkHolder, tasks); } @@ -3057,7 +2438,7 @@ index e944bd32df98a1dd7dde8c5ce3698b6d81862cbf..c185a85e18932ac8ee96a01779e1e1a5 } } } finally { -@@ -467,7 +509,7 @@ public final class ChunkTaskScheduler { +@@ -565,7 +566,7 @@ public final class ChunkTaskScheduler { tasks.get(i).schedule(); } @@ -3066,7 +2447,7 @@ index e944bd32df98a1dd7dde8c5ce3698b6d81862cbf..c185a85e18932ac8ee96a01779e1e1a5 // couldn't schedule try { loadCallback.accept(chunk); -@@ -656,7 +698,7 @@ public final class ChunkTaskScheduler { +@@ -754,7 +755,7 @@ public final class ChunkTaskScheduler { */ @Deprecated public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run) { @@ -3075,7 +2456,7 @@ index e944bd32df98a1dd7dde8c5ce3698b6d81862cbf..c185a85e18932ac8ee96a01779e1e1a5 } /** -@@ -664,7 +706,7 @@ public final class ChunkTaskScheduler { +@@ -762,7 +763,7 @@ public final class ChunkTaskScheduler { */ @Deprecated public PrioritisedExecutor.PrioritisedTask scheduleChunkTask(final Runnable run, final PrioritisedExecutor.Priority priority) { @@ -3084,7 +2465,7 @@ index e944bd32df98a1dd7dde8c5ce3698b6d81862cbf..c185a85e18932ac8ee96a01779e1e1a5 } public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run) { -@@ -673,28 +715,33 @@ public final class ChunkTaskScheduler { +@@ -771,28 +772,33 @@ public final class ChunkTaskScheduler { public PrioritisedExecutor.PrioritisedTask createChunkTask(final int chunkX, final int chunkZ, final Runnable run, final PrioritisedExecutor.Priority priority) { @@ -3101,17 +2482,8 @@ index e944bd32df98a1dd7dde8c5ce3698b6d81862cbf..c185a85e18932ac8ee96a01779e1e1a5 final PrioritisedExecutor.Priority priority) { - return this.mainThreadExecutor.queueRunnable(run, priority); + return MinecraftServer.getServer().regionizedServer.taskQueue.queueChunkTask(this.world, chunkX, chunkZ, run, priority); // Folia - regionised ticking - } - -- public void executeTasksUntil(final BooleanSupplier exit) { -- if (Bukkit.isPrimaryThread()) { -- this.mainThreadExecutor.executeConditionally(exit); -- } else { -- long counter = 1L; -- while (!exit.getAsBoolean()) { -- counter = ConcurrentUtil.linearLongBackoff(counter, 100_000L, 5_000_000L); // 100us, 5ms -- } -- } ++ } ++ + // Folia start - region threading + // this function is guaranteed to never touch the ticket lock or schedule lock + // yes, this IS a hack so that we can avoid deadlock due to region threading introducing the @@ -3125,24 +2497,34 @@ index e944bd32df98a1dd7dde8c5ce3698b6d81862cbf..c185a85e18932ac8ee96a01779e1e1a5 + return ret; } + // Folia end - region threading -+ + +- public void executeTasksUntil(final BooleanSupplier exit) { +- if (Bukkit.isPrimaryThread()) { +- this.mainThreadExecutor.executeConditionally(exit); +- } else { +- long counter = 1L; +- while (!exit.getAsBoolean()) { +- counter = ConcurrentUtil.linearLongBackoff(counter, 100_000L, 5_000_000L); // 100us, 5ms +- } +- } +- } + // Folia - regionised ticking public boolean halt(final boolean sync, final long maxWaitNS) { - this.lightExecutor.halt(); -@@ -703,6 +750,7 @@ public final class ChunkTaskScheduler { + this.radiusAwareGenExecutor.halt(); +@@ -800,6 +806,7 @@ public final class ChunkTaskScheduler { this.loadExecutor.halt(); final long time = System.nanoTime(); if (sync) { + // start at 10 * 0.5ms -> 5ms for (long failures = 9L;; failures = ConcurrentUtil.linearLongBackoff(failures, 500_000L, 50_000_000L)) { if ( - !this.lightExecutor.isActive() && + !this.radiusAwareGenExecutor.isActive() && diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java -index 8013dd333e27aa5fd0beb431fa32491eec9f5246..3b70ccd8e0b1ada943f57faf99c23b2935249cf6 100644 +index 51304c5cf4b0ac7646693ef97ef4a3847d3342b5..f1c68d9850ece7532a8607db955eaa4fc3a4bf05 100644 --- a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java +++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java -@@ -708,7 +708,7 @@ public final class NewChunkHolder { +@@ -726,7 +726,7 @@ public final class NewChunkHolder { boolean killed; // must hold scheduling lock @@ -3151,7 +2533,7 @@ index 8013dd333e27aa5fd0beb431fa32491eec9f5246..3b70ccd8e0b1ada943f57faf99c23b29 if (this.killed) { return; } -@@ -1412,7 +1412,7 @@ public final class NewChunkHolder { +@@ -1440,7 +1440,7 @@ public final class NewChunkHolder { } // must be scheduled to main, we do not trust the callback to not do anything stupid @@ -3160,7 +2542,7 @@ index 8013dd333e27aa5fd0beb431fa32491eec9f5246..3b70ccd8e0b1ada943f57faf99c23b29 for (final Consumer consumer : consumers) { try { consumer.accept(chunk); -@@ -1455,7 +1455,7 @@ public final class NewChunkHolder { +@@ -1483,7 +1483,7 @@ public final class NewChunkHolder { } // must be scheduled to main, we do not trust the callback to not do anything stupid @@ -3169,7 +2551,7 @@ index 8013dd333e27aa5fd0beb431fa32491eec9f5246..3b70ccd8e0b1ada943f57faf99c23b29 for (final Consumer consumer : consumers) { try { consumer.accept(chunk); -@@ -1715,7 +1715,7 @@ public final class NewChunkHolder { +@@ -1744,7 +1744,7 @@ public final class NewChunkHolder { return this.entityChunk; } @@ -3178,7 +2560,7 @@ index 8013dd333e27aa5fd0beb431fa32491eec9f5246..3b70ccd8e0b1ada943f57faf99c23b29 public static final record SaveStat(boolean savedChunk, boolean savedEntityChunk, boolean savedPoiChunk) {} -@@ -1865,7 +1865,7 @@ public final class NewChunkHolder { +@@ -1894,7 +1894,7 @@ public final class NewChunkHolder { } catch (final ThreadDeath death) { throw death; } catch (final Throwable thr) { @@ -3187,7 +2569,7 @@ index 8013dd333e27aa5fd0beb431fa32491eec9f5246..3b70ccd8e0b1ada943f57faf99c23b29 if (unloading && !completing) { this.completeAsyncChunkDataSave(null); } -@@ -1913,7 +1913,7 @@ public final class NewChunkHolder { +@@ -1942,7 +1942,7 @@ public final class NewChunkHolder { } catch (final ThreadDeath death) { throw death; } catch (final Throwable thr) { @@ -3196,7 +2578,7 @@ index 8013dd333e27aa5fd0beb431fa32491eec9f5246..3b70ccd8e0b1ada943f57faf99c23b29 } return true; -@@ -1939,7 +1939,7 @@ public final class NewChunkHolder { +@@ -1968,7 +1968,7 @@ public final class NewChunkHolder { } catch (final ThreadDeath death) { throw death; } catch (final Throwable thr) { @@ -3246,7 +2628,7 @@ index cd2e4d792e972b8bf1e07b8961594a670ae949cf..3ab8dbf2768a4ef8fb53af6f5431f7f6 } diff --git a/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java -index 99c41a39cdad0271d089c6e03bebfdafba1aaa57..41aaa709dc2e474f23e759ebc51f33021c4f5485 100644 +index d3b39d88a72ca25057fd8574d32f28db0d420818..41aaa709dc2e474f23e759ebc51f33021c4f5485 100644 --- a/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java +++ b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java @@ -46,7 +46,7 @@ public final class MobcapsCommand implements PaperSubcommand { @@ -3293,7 +2675,7 @@ index 99c41a39cdad0271d089c6e03bebfdafba1aaa57..41aaa709dc2e474f23e759ebc51f3302 - } - - final ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); -- final ServerLevel level = serverPlayer.getLevel(); +- final ServerLevel level = serverPlayer.serverLevel(); - - if (!level.paperConfig().entities.spawning.perPlayerMobSpawns) { - sender.sendMessage(Component.text("Use '/paper mobcaps' for worlds where per-player mob spawning is disabled.", NamedTextColor.RED)); @@ -3325,10 +2707,10 @@ index bd68139ae635f2ad7ec8e7a21e0056a139c4c62e..48a43341b17247355a531164019d5cc9 } diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -index 9f5f0d8ddc8f480b48079c70e38c9c08eff403f6..3b83f25a24d6f9cdbf131d5a4432fb4ad018be4e 100644 +index 77d05f7efdcdceef681a75692c208075d873d368..921666fdffc02e9b0420e1a06650c546376eeeea 100644 --- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -288,6 +288,18 @@ public class GlobalConfiguration extends ConfigurationPart { +@@ -273,6 +273,18 @@ public class GlobalConfiguration extends ConfigurationPart { public boolean strictAdvancementDimensionCheck = false; } @@ -5973,10 +5355,10 @@ index 0000000000000000000000000000000000000000..84b4ff07735fb84e28ee8966ffdedb1b +} diff --git a/src/main/java/io/papermc/paper/threadedregions/ThreadedRegionizer.java b/src/main/java/io/papermc/paper/threadedregions/ThreadedRegionizer.java new file mode 100644 -index 0000000000000000000000000000000000000000..5170b43743ea27a5c2aaee37d76f4e7e730fd808 +index 0000000000000000000000000000000000000000..4b8f51cd8ebd0bb85f1f6035650488da6454212d --- /dev/null +++ b/src/main/java/io/papermc/paper/threadedregions/ThreadedRegionizer.java -@@ -0,0 +1,1309 @@ +@@ -0,0 +1,1318 @@ +package io.papermc.paper.threadedregions; + +import ca.spottedleaf.concurrentutil.map.SWMRLong2ObjectHashTable; @@ -5987,6 +5369,7 @@ index 0000000000000000000000000000000000000000..5170b43743ea27a5c2aaee37d76f4e7e +import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongComparator; ++import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; @@ -6653,6 +6036,14 @@ index 0000000000000000000000000000000000000000..5170b43743ea27a5c2aaee37d76f4e7e + } + } + ++ /** ++ * returns an iterator directly over the sections map. This is only to be used by a thread which is _ticking_ ++ * 'this' region. ++ */ ++ public LongIterator getOwnedSectionsUnsynchronised() { ++ return this.sectionByKey.keySet().iterator(); ++ } ++ + public LongArrayList getOwnedChunks() { + final boolean lock = this.regioniser.writeLockOwner != Thread.currentThread(); + if (lock) { @@ -8197,13 +7588,14 @@ index 0000000000000000000000000000000000000000..ee9f5e1f3387998cddbeb1dc6dc6e2b1 + } +} diff --git a/src/main/java/io/papermc/paper/threadedregions/TickRegions.java b/src/main/java/io/papermc/paper/threadedregions/TickRegions.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6c76c70574642aa4f3a8fce74e4608781ce132ec ---- /dev/null +index d5d39e9c1f326e91010237b0db80d527ac52f4d6..e32529bfda0422ad884ee828a0610a3229dc02d2 100644 +--- a/src/main/java/io/papermc/paper/threadedregions/TickRegions.java +++ b/src/main/java/io/papermc/paper/threadedregions/TickRegions.java -@@ -0,0 +1,392 @@ -+package io.papermc.paper.threadedregions; -+ +@@ -1,9 +1,393 @@ + package io.papermc.paper.threadedregions; + +-// placeholder class for Folia +-public class TickRegions { +import ca.spottedleaf.concurrentutil.scheduler.SchedulerThreadPool; +import ca.spottedleaf.concurrentutil.util.TimeUtil; +import com.mojang.logging.LogUtils; @@ -8226,10 +7618,10 @@ index 0000000000000000000000000000000000000000..6c76c70574642aa4f3a8fce74e460878 +public final class TickRegions implements ThreadedRegionizer.RegionCallbacks { + + private static final Logger LOGGER = LogUtils.getLogger(); -+ -+ public static int getRegionChunkShift() { -+ return 4; -+ } + + public static int getRegionChunkShift() { + return 4; + } + + private static boolean initialised; + private static TickRegionScheduler scheduler; @@ -8593,7 +7985,8 @@ index 0000000000000000000000000000000000000000..6c76c70574642aa4f3a8fce74e460878 + return this.region.taskQueueData.hasTasks(); + } + } -+} ++>>>>>>> Threaded Regions + } diff --git a/src/main/java/io/papermc/paper/threadedregions/commands/CommandServerHealth.java b/src/main/java/io/papermc/paper/threadedregions/commands/CommandServerHealth.java new file mode 100644 index 0000000000000000000000000000000000000000..3bcb1dc98c61e025874cc9e008faa722581a530c @@ -10532,7 +9925,7 @@ index 413e4b6da027876dbbe8eb78f2568a440f431547..3a7dbcb9964723b8ed5e6b0a1ee42679 throw new RuntimeException(); } diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java -index c856a9a0d085b278da416c59996fc131811f790c..915cbf8c02c4bba0c62e5589229ee27e30535c07 100644 +index 2d11a67bdc82088abf0b3ca134f352f155c8eb1f..38441121cf7cdc1d64ef9fc17ae76dc16fbf96f2 100644 --- a/src/main/java/io/papermc/paper/util/MCUtil.java +++ b/src/main/java/io/papermc/paper/util/MCUtil.java @@ -29,6 +29,7 @@ import net.minecraft.world.level.ClipContext; @@ -10583,10 +9976,10 @@ index c856a9a0d085b278da416c59996fc131811f790c..915cbf8c02c4bba0c62e5589229ee27e * Converts an NMS entity's current location to a Bukkit Location * @param entity diff --git a/src/main/java/io/papermc/paper/util/TickThread.java b/src/main/java/io/papermc/paper/util/TickThread.java -index fc57850b80303fcade89ca95794f63910404a407..294ea54d59e1e0b00f67d1623a5c807f9d368df6 100644 +index f9063e2282f89e97a378f06822cde0a64ab03f9a..cb453dd110fc37fae75257a4576512126207763e 100644 --- a/src/main/java/io/papermc/paper/util/TickThread.java +++ b/src/main/java/io/papermc/paper/util/TickThread.java -@@ -1,8 +1,22 @@ +@@ -1,5 +1,11 @@ package io.papermc.paper.util; +import io.papermc.paper.threadedregions.RegionShutdownThread; @@ -10595,105 +9988,58 @@ index fc57850b80303fcade89ca95794f63910404a407..294ea54d59e1e0b00f67d1623a5c807f +import io.papermc.paper.threadedregions.ThreadedRegionizer; +import io.papermc.paper.threadedregions.TickRegionScheduler; +import io.papermc.paper.threadedregions.TickRegions; -+import net.minecraft.core.BlockPos; + import net.minecraft.core.BlockPos; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.network.ServerGamePacketListenerImpl; -+import net.minecraft.util.Mth; - import net.minecraft.world.entity.Entity; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.phys.AABB; -+import net.minecraft.world.phys.Vec3; - import org.bukkit.Bukkit; - import java.util.concurrent.atomic.AtomicInteger; - -@@ -38,6 +52,20 @@ public class TickThread extends Thread { - } +@@ -114,46 +120,125 @@ public class TickThread extends Thread { } -+ public static void ensureTickThread(final ServerLevel world, final BlockPos pos, final String reason) { -+ if (!isTickThreadFor(world, pos)) { -+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -+ throw new IllegalStateException(reason); -+ } -+ } -+ -+ public static void ensureTickThread(final ServerLevel world, final ChunkPos pos, final String reason) { -+ if (!isTickThreadFor(world, pos)) { -+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -+ throw new IllegalStateException(reason); -+ } -+ } -+ - public static void ensureTickThread(final ServerLevel world, final int chunkX, final int chunkZ, final String reason) { - if (!isTickThreadFor(world, chunkX, chunkZ)) { - MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -@@ -52,6 +80,20 @@ public class TickThread extends Thread { - } - } - -+ public static void ensureTickThread(final ServerLevel world, final AABB aabb, final String reason) { -+ if (!isTickThreadFor(world, aabb)) { -+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -+ throw new IllegalStateException(reason); -+ } -+ } -+ -+ public static void ensureTickThread(final ServerLevel world, final double blockX, final double blockZ, final String reason) { -+ if (!isTickThreadFor(world, blockX, blockZ)) { -+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -+ throw new IllegalStateException(reason); -+ } -+ } -+ - public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */ - - private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); -@@ -77,11 +119,126 @@ public class TickThread extends Thread { - return Thread.currentThread() instanceof TickThread; - } - -+ public static boolean isShutdownThread() { + public static boolean isShutdownThread() { +- return false; + return Thread.currentThread().getClass() == RegionShutdownThread.class; -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final BlockPos pos) { + } + + public static boolean isTickThreadFor(final ServerLevel world, final BlockPos pos) { +- return isTickThread(); + return isTickThreadFor(world, pos.getX() >> 4, pos.getZ() >> 4); -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final ChunkPos pos) { + } + + public static boolean isTickThreadFor(final ServerLevel world, final ChunkPos pos) { +- return isTickThread(); + return isTickThreadFor(world, pos.x, pos.z); -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final Vec3 pos) { + } + + public static boolean isTickThreadFor(final ServerLevel world, final Vec3 pos) { +- return isTickThread(); + return isTickThreadFor(world, Mth.floor(pos.x) >> 4, Mth.floor(pos.z) >> 4); -+ } -+ + } + public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ) { -- return Thread.currentThread() instanceof TickThread; +- return isTickThread(); + final ThreadedRegionizer.ThreadedRegion region = + TickRegionScheduler.getCurrentRegion(); + if (region == null) { + return isShutdownThread(); + } + return world.regioniser.getRegionAtUnsynchronised(chunkX, chunkZ) == region; -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final AABB aabb) { + } + + public static boolean isTickThreadFor(final ServerLevel world, final AABB aabb) { +- return isTickThread(); + return isTickThreadFor( + world, + CoordinateUtils.getChunkCoordinate(aabb.minX), CoordinateUtils.getChunkCoordinate(aabb.minZ), + CoordinateUtils.getChunkCoordinate(aabb.maxX), CoordinateUtils.getChunkCoordinate(aabb.maxZ) + ); -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final double blockX, final double blockZ) { + } + + public static boolean isTickThreadFor(final ServerLevel world, final double blockX, final double blockZ) { +- return isTickThread(); + return isTickThreadFor(world, CoordinateUtils.getChunkCoordinate(blockX), CoordinateUtils.getChunkCoordinate(blockZ)); -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final Vec3 position, final Vec3 deltaMovement, final int buffer) { + } + + public static boolean isTickThreadFor(final ServerLevel world, final Vec3 position, final Vec3 deltaMovement, final int buffer) { +- return isTickThread(); + final int fromChunkX = CoordinateUtils.getChunkX(position); + final int fromChunkZ = CoordinateUtils.getChunkZ(position); + @@ -10708,9 +10054,10 @@ index fc57850b80303fcade89ca95794f63910404a407..294ea54d59e1e0b00f67d1623a5c807f + Math.max(fromChunkX, toChunkX) + buffer, + Math.max(fromChunkZ, toChunkZ) + buffer + ); -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) { + } + + public static boolean isTickThreadFor(final ServerLevel world, final int fromChunkX, final int fromChunkZ, final int toChunkX, final int toChunkZ) { +- return isTickThread(); + final ThreadedRegionizer.ThreadedRegion region = + TickRegionScheduler.getCurrentRegion(); + if (region == null) { @@ -10735,14 +10082,15 @@ index fc57850b80303fcade89ca95794f63910404a407..294ea54d59e1e0b00f67d1623a5c807f + } + + return true; -+ } -+ -+ public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ, final int radius) { + } + + public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ, final int radius) { +- return isTickThread(); + return isTickThreadFor(world, chunkX - radius, chunkZ - radius, chunkX + radius, chunkZ + radius); } public static boolean isTickThreadFor(final Entity entity) { -- return Thread.currentThread() instanceof TickThread; +- return isTickThread(); + if (entity == null) { + return true; + } @@ -10759,7 +10107,7 @@ index fc57850b80303fcade89ca95794f63910404a407..294ea54d59e1e0b00f67d1623a5c807f + return isShutdownThread(); + } + -+ final Level level = entity.level; ++ final Level level = entity.level(); + if (level != region.regioniser.world) { + // world mismatch + return false; @@ -11091,23 +10439,23 @@ index f40d6eaa6ebbd775cd3feb41546423fe4cbf2b22..e43c95e3be6cb41eab0a1cecbf154350 } catch (ParseException var3) { throw new JsonSyntaxException("Invalid datetime: " + datetime, var3); diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java -index 7b6b51392b123d34382233adcf4c3d4867bdaa32..4c793605566f1755c99496c00dc1ef4f38c4ac7d 100644 +index 22884a2b148b9a5af8655bb754ebe73618218a83..2183c3b68808bfd489e3cb7223567ff2ec5c895b 100644 --- a/src/main/java/net/minecraft/commands/CommandSourceStack.java +++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java -@@ -66,7 +66,7 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy +@@ -69,7 +69,7 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy public CommandSourceStack(CommandSource output, Vec3 pos, Vec2 rot, ServerLevel world, int level, String name, Component displayName, MinecraftServer server, @Nullable Entity entity) { this(output, pos, rot, world, level, name, displayName, server, entity, false, (commandcontext, flag, j) -> { -- }, EntityAnchorArgument.Anchor.FEET, CommandSigningContext.ANONYMOUS, TaskChainer.immediate(server)); -+ }, EntityAnchorArgument.Anchor.FEET, CommandSigningContext.ANONYMOUS, TaskChainer.immediate((Runnable run) -> { io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(run);})); // Folia - region threading +- }, EntityAnchorArgument.Anchor.FEET, CommandSigningContext.ANONYMOUS, TaskChainer.immediate(server), (j) -> { ++ }, EntityAnchorArgument.Anchor.FEET, CommandSigningContext.ANONYMOUS, TaskChainer.immediate((Runnable run) -> { io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(run);}), (j) -> { // Folia - region threading + }); } - protected CommandSourceStack(CommandSource output, Vec3 pos, Vec2 rot, ServerLevel world, int level, String name, Component displayName, MinecraftServer server, @Nullable Entity entity, boolean silent, @Nullable ResultConsumer consumer, EntityAnchorArgument.Anchor entityAnchor, CommandSigningContext signedArguments, TaskChainer messageChainTaskQueue) { diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index 87cc7562e4a166d078fe11b7f6980497fc0bd33e..e970a9807f1497b8b9f4155f558f4fcb719cb368 100644 +index 80c2c8d565f03ae0ea24fbdecdbe2bc5b9aa4b82..681cd79ce24fe5d952e987d46e2fd8df07a0f8a1 100644 --- a/src/main/java/net/minecraft/commands/Commands.java +++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -143,13 +143,13 @@ public class Commands { +@@ -144,13 +144,13 @@ public class Commands { AdvancementCommands.register(this.dispatcher); AttributeCommand.register(this.dispatcher, commandRegistryAccess); ExecuteCommand.register(this.dispatcher, commandRegistryAccess); @@ -11126,7 +10474,7 @@ index 87cc7562e4a166d078fe11b7f6980497fc0bd33e..e970a9807f1497b8b9f4155f558f4fcb DefaultGameModeCommands.register(this.dispatcher); DifficultyCommand.register(this.dispatcher); EffectCommands.register(this.dispatcher, commandRegistryAccess); -@@ -159,45 +159,45 @@ public class Commands { +@@ -160,46 +160,46 @@ public class Commands { FillCommand.register(this.dispatcher, commandRegistryAccess); FillBiomeCommand.register(this.dispatcher, commandRegistryAccess); ForceLoadCommand.register(this.dispatcher); @@ -11151,7 +10499,9 @@ index 87cc7562e4a166d078fe11b7f6980497fc0bd33e..e970a9807f1497b8b9f4155f558f4fcb - ReloadCommand.register(this.dispatcher); + //ReloadCommand.register(this.dispatcher); // Folia - region threading RecipeCommand.register(this.dispatcher); +- ReturnCommand.register(this.dispatcher); - RideCommand.register(this.dispatcher); ++ //ReturnCommand.register(this.dispatcher); // Folia - region threading - TODO later + //RideCommand.register(this.dispatcher); // Folia - region threading - TODO later SayCommand.register(this.dispatcher); - ScheduleCommand.register(this.dispatcher); @@ -11186,7 +10536,7 @@ index 87cc7562e4a166d078fe11b7f6980497fc0bd33e..e970a9807f1497b8b9f4155f558f4fcb if (JvmProfiler.INSTANCE.isAvailable()) { JfrCommand.register(this.dispatcher); } -@@ -215,8 +215,8 @@ public class Commands { +@@ -217,8 +217,8 @@ public class Commands { OpCommand.register(this.dispatcher); PardonCommand.register(this.dispatcher); PardonIpCommand.register(this.dispatcher); @@ -11197,7 +10547,7 @@ index 87cc7562e4a166d078fe11b7f6980497fc0bd33e..e970a9807f1497b8b9f4155f558f4fcb SaveOffCommand.register(this.dispatcher); SaveOnCommand.register(this.dispatcher); SetPlayerIdleTimeoutCommand.register(this.dispatcher); -@@ -446,9 +446,12 @@ public class Commands { +@@ -448,9 +448,12 @@ public class Commands { } // Paper start - Async command map building new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper @@ -11227,13 +10577,13 @@ index 309ad5a1da6b3a297d5526cd9247359ac5f49406..5a85fcbcd2966af95683106d4f459653 } diff --git a/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java -index 958134519befadc27a5b647caf64acf272ee2db4..a0712ad55c8e02a88ddf55bb0e70e05dc1ddbcdc 100644 +index 573244877f096c4ff4c68f7fcfd21f7423da1104..fd2cc58e6cec72b0ec77af79c12e155efba568e3 100644 --- a/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java -@@ -58,7 +58,7 @@ public class BoatDispenseItemBehavior extends DefaultDispenseItemBehavior { +@@ -60,7 +60,7 @@ public class BoatDispenseItemBehavior extends DefaultDispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); - BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d0, d1 + d3, d2)); + BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d1, d2 + d4, d3)); - if (!DispenserBlock.eventFired) { + if (!DispenserBlock.eventFired.get()) { // Folia - region threading worldserver.getCraftServer().getPluginManager().callEvent(event); @@ -11253,10 +10603,10 @@ index 1e6ba6d9cceda1d4867b183c3dbc03d317ed287f..de8cf0f0d34708b960f1c81cb10d813a } diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab8f73481b 100644 +index b4f5dbe9022dd20437c15c4f6fbe2ac06dacbadb..d4480c4f10e4e412259d1a8493199f36e56022a9 100644 --- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -223,7 +223,7 @@ public interface DispenseItemBehavior { +@@ -222,7 +222,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); @@ -11265,7 +10615,7 @@ index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab worldserver.getCraftServer().getPluginManager().callEvent(event); } -@@ -278,7 +278,7 @@ public interface DispenseItemBehavior { +@@ -277,7 +277,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); @@ -11274,7 +10624,7 @@ index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab worldserver.getCraftServer().getPluginManager().callEvent(event); } -@@ -334,7 +334,7 @@ public interface DispenseItemBehavior { +@@ -333,7 +333,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) list.get(0).getBukkitEntity()); @@ -11283,7 +10633,7 @@ index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab world.getCraftServer().getPluginManager().callEvent(event); } -@@ -390,7 +390,7 @@ public interface DispenseItemBehavior { +@@ -389,7 +389,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) entityhorseabstract.getBukkitEntity()); @@ -11292,7 +10642,7 @@ index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab world.getCraftServer().getPluginManager().callEvent(event); } -@@ -464,7 +464,7 @@ public interface DispenseItemBehavior { +@@ -463,7 +463,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) entityhorsechestedabstract.getBukkitEntity()); @@ -11301,7 +10651,7 @@ index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab world.getCraftServer().getPluginManager().callEvent(event); } -@@ -503,7 +503,7 @@ public interface DispenseItemBehavior { +@@ -502,7 +502,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(enumdirection.getStepX(), enumdirection.getStepY(), enumdirection.getStepZ())); @@ -11310,7 +10660,7 @@ index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab worldserver.getCraftServer().getPluginManager().callEvent(event); } -@@ -561,7 +561,7 @@ public interface DispenseItemBehavior { +@@ -560,7 +560,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d3, d4, d5)); @@ -11319,7 +10669,7 @@ index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab worldserver.getCraftServer().getPluginManager().callEvent(event); } -@@ -635,7 +635,7 @@ public interface DispenseItemBehavior { +@@ -633,7 +633,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(x, y, z)); @@ -11328,7 +10678,7 @@ index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab worldserver.getCraftServer().getPluginManager().callEvent(event); } -@@ -708,7 +708,7 @@ public interface DispenseItemBehavior { +@@ -706,7 +706,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ())); @@ -11337,7 +10687,7 @@ index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab worldserver.getCraftServer().getPluginManager().callEvent(event); } -@@ -755,7 +755,7 @@ public interface DispenseItemBehavior { +@@ -753,7 +753,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack); // Paper - ignore stack size on damageable items BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); @@ -11346,7 +10696,7 @@ index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab worldserver.getCraftServer().getPluginManager().callEvent(event); } -@@ -816,7 +816,7 @@ public interface DispenseItemBehavior { +@@ -814,7 +814,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0)); @@ -11355,7 +10705,7 @@ index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab worldserver.getCraftServer().getPluginManager().callEvent(event); } -@@ -834,7 +834,8 @@ public interface DispenseItemBehavior { +@@ -832,7 +832,8 @@ public interface DispenseItemBehavior { } } @@ -11365,7 +10715,7 @@ index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab // CraftBukkit end if (!BoneMealItem.growCrop(stack, worldserver, blockposition) && !BoneMealItem.growWaterPlant(stack, worldserver, blockposition, (Direction) null)) { -@@ -843,13 +844,13 @@ public interface DispenseItemBehavior { +@@ -841,13 +842,13 @@ public interface DispenseItemBehavior { worldserver.levelEvent(1505, blockposition, 0); } // CraftBukkit start @@ -11385,7 +10735,7 @@ index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab StructureGrowEvent structureEvent = null; if (treeType != null) { structureEvent = new StructureGrowEvent(location, treeType, false, null, blocks); -@@ -884,7 +885,7 @@ public interface DispenseItemBehavior { +@@ -882,7 +883,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1); BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D)); @@ -11394,7 +10744,7 @@ index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab worldserver.getCraftServer().getPluginManager().callEvent(event); } -@@ -941,7 +942,7 @@ public interface DispenseItemBehavior { +@@ -939,7 +940,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ())); @@ -11403,7 +10753,7 @@ index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab worldserver.getCraftServer().getPluginManager().callEvent(event); } -@@ -990,7 +991,7 @@ public interface DispenseItemBehavior { +@@ -988,7 +989,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - single item in event BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ())); @@ -11412,7 +10762,7 @@ index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab worldserver.getCraftServer().getPluginManager().callEvent(event); } -@@ -1063,7 +1064,7 @@ public interface DispenseItemBehavior { +@@ -1061,7 +1062,7 @@ public interface DispenseItemBehavior { CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack.copyWithCount(1)); // Paper - only single item in event BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ())); @@ -11422,7 +10772,7 @@ index 88d18d18d69876c98e199acb647c6cca9448d55d..d2e5899883ed62cc41d1bc3c4ca5c6ab } diff --git a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java -index d1127d93a85a837933d0d73c24cacac4adc3a5b9..ac9f4f2ac817e5fe9a15759c549a57ad8473b6ac 100644 +index 9b0049dfeaec9b688bf276f2ac2b18943b5696b2..a9cd926d5b7177e7d9bf35ed1614e2c917b16931 100644 --- a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java @@ -40,7 +40,7 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior { @@ -11448,10 +10798,10 @@ index 0159ed9cbc644c39fa79e62327f13375193fdc98..a930c8eb64d6c7044646d6b0156e202e } diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index f9e10bf048929886db3c414038d2c7e9f84226a6..7861c07a9ade391002f08da860e396fcd832b0fe 100644 +index c45c1d519aba414557bf1a4c9260f928bd8d9b14..cf9719ae27d7f11a23637f2507da79b1e5cff9bd 100644 --- a/src/main/java/net/minecraft/network/Connection.java +++ b/src/main/java/net/minecraft/network/Connection.java -@@ -75,7 +75,7 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -73,7 +73,7 @@ public class Connection extends SimpleChannelInboundHandler> { return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper }); private final PacketFlow receiving; @@ -11460,7 +10810,7 @@ index f9e10bf048929886db3c414038d2c7e9f84226a6..7861c07a9ade391002f08da860e396fc public Channel channel; public SocketAddress address; // Spigot Start -@@ -83,7 +83,7 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -81,7 +81,7 @@ public class Connection extends SimpleChannelInboundHandler> { public com.mojang.authlib.properties.Property[] spoofedProfile; public boolean preparing = true; // Spigot End @@ -11469,7 +10819,7 @@ index f9e10bf048929886db3c414038d2c7e9f84226a6..7861c07a9ade391002f08da860e396fc private Component disconnectedReason; private boolean encrypted; private boolean disconnectionHandled; -@@ -179,6 +179,32 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -177,6 +177,32 @@ public class Connection extends SimpleChannelInboundHandler> { this.receiving = side; } @@ -11502,7 +10852,7 @@ index f9e10bf048929886db3c414038d2c7e9f84226a6..7861c07a9ade391002f08da860e396fc public void channelActive(ChannelHandlerContext channelhandlercontext) throws Exception { super.channelActive(channelhandlercontext); this.channel = channelhandlercontext.channel(); -@@ -193,6 +219,7 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -191,6 +217,7 @@ public class Connection extends SimpleChannelInboundHandler> { Connection.LOGGER.error(LogUtils.FATAL_MARKER, "Failed to change protocol to handshake", throwable); } @@ -11510,7 +10860,7 @@ index f9e10bf048929886db3c414038d2c7e9f84226a6..7861c07a9ade391002f08da860e396fc } public void setProtocol(ConnectionProtocol state) { -@@ -378,13 +405,6 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -376,13 +403,6 @@ public class Connection extends SimpleChannelInboundHandler> { return; // Do nothing } packet.onPacketDispatch(getPlayer()); @@ -11524,7 +10874,7 @@ index f9e10bf048929886db3c414038d2c7e9f84226a6..7861c07a9ade391002f08da860e396fc // write the packets to the queue, then flush - antixray hooks there already java.util.List extraPackets = InnerUtil.buildExtraPackets(packet); boolean hasExtraPackets = extraPackets != null && !extraPackets.isEmpty(); -@@ -506,66 +526,58 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -504,66 +524,58 @@ public class Connection extends SimpleChannelInboundHandler> { // Paper start - rewrite this to be safer if ran off main thread private boolean flushQueue() { // void -> boolean @@ -11630,7 +10980,7 @@ index f9e10bf048929886db3c414038d2c7e9f84226a6..7861c07a9ade391002f08da860e396fc } // Paper end -@@ -574,21 +586,41 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -572,21 +584,41 @@ public class Connection extends SimpleChannelInboundHandler> { private static int currTick; // Paper public void tick() { this.flushQueue(); @@ -11680,7 +11030,7 @@ index f9e10bf048929886db3c414038d2c7e9f84226a6..7861c07a9ade391002f08da860e396fc // Paper start - detailed watchdog information net.minecraft.network.protocol.PacketUtils.packetProcessing.push(this.packetListener); try { // Paper end - detailed watchdog information -@@ -628,13 +660,21 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -626,13 +658,21 @@ public class Connection extends SimpleChannelInboundHandler> { // Paper start public void clearPacketQueue() { net.minecraft.server.level.ServerPlayer player = getPlayer(); @@ -11705,7 +11055,7 @@ index f9e10bf048929886db3c414038d2c7e9f84226a6..7861c07a9ade391002f08da860e396fc } // Paper end public void disconnect(Component disconnectReason) { -@@ -646,6 +686,7 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -644,6 +684,7 @@ public class Connection extends SimpleChannelInboundHandler> { this.channel.close(); // We can't wait as this may be called from an event loop. this.disconnectedReason = disconnectReason; } @@ -11713,7 +11063,7 @@ index f9e10bf048929886db3c414038d2c7e9f84226a6..7861c07a9ade391002f08da860e396fc } -@@ -805,13 +846,27 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -806,13 +847,27 @@ public class Connection extends SimpleChannelInboundHandler> { final net.minecraft.server.network.ServerGamePacketListenerImpl playerConnection = (net.minecraft.server.network.ServerGamePacketListenerImpl) packetListener; new com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent(playerConnection.player.getUUID(), playerConnection.player.getScoreboardName(), ((java.net.InetSocketAddress)address).getAddress(), false).callEvent(); @@ -11786,7 +11136,7 @@ index d2f0a0755317f5fa9a1ccf7db346aa77fd287d80..b07df826a3028c14b48b09dbaeccc907 // 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 3219482b96cab8262e393a790c88d903d7de5166..3478d9c1db9acf19165df7308b6ae4461fa8fef7 100644 +index cdb9925e8c4771831a7fe8bbcb705278c51aa0d2..37121e7be9ed0056b50b8d01dff84ee8660bab47 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -242,7 +242,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { return worldserver + " " + worldserver.dimension().location(); -@@ -1543,7 +1676,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop attribute, double multiplier) throws CommandSyntaxException { - LivingEntity livingEntity = getEntityWithAttribute(target, attribute); - double d = livingEntity.getAttributeValue(attribute); -- source.sendSuccess(Component.translatable("commands.attribute.value.get.success", getAttributeDescription(attribute), target.getName(), d), false); +- source.sendSuccess(() -> { +- return Component.translatable("commands.attribute.value.get.success", getAttributeDescription(attribute), target.getName(), d); +- }, false); - return (int)(d * multiplier); + // Folia start - region threading + target.getBukkitEntity().taskScheduler.schedule((LivingEntity nmsEntity) -> { + try { -+ LivingEntity livingEntity = getEntityWithAttribute(nmsEntity, attribute); // Folia - region threading ++ LivingEntity livingEntity = getEntityWithAttribute(nmsEntity, attribute); + double d = livingEntity.getAttributeValue(attribute); -+ source.sendSuccess(Component.translatable("commands.attribute.value.get.success", getAttributeDescription(attribute), nmsEntity.getName(), d), false); ++ source.sendSuccess(() -> { ++ return Component.translatable("commands.attribute.value.get.success", getAttributeDescription(attribute), nmsEntity.getName(), d); ++ }, false); + } catch (CommandSyntaxException ex) { + sendMessage(source, ex); + } @@ -12525,21 +11878,24 @@ index e846bd5db018f79c083d29f8f7b305a3d7ab45f5..b01aeb7bae3b6d3d291f76d19e880798 private static int getAttributeBase(CommandSourceStack source, Entity target, Holder attribute, double multiplier) throws CommandSyntaxException { - LivingEntity livingEntity = getEntityWithAttribute(target, attribute); - double d = livingEntity.getAttributeBaseValue(attribute); -- source.sendSuccess(Component.translatable("commands.attribute.base_value.get.success", getAttributeDescription(attribute), target.getName(), d), false); +- source.sendSuccess(() -> { +- return Component.translatable("commands.attribute.base_value.get.success", getAttributeDescription(attribute), target.getName(), d); +- }, false); - return (int)(d * multiplier); + // Folia start - region threading + target.getBukkitEntity().taskScheduler.schedule((LivingEntity nmsEntity) -> { + try { + LivingEntity livingEntity = getEntityWithAttribute(nmsEntity, attribute); + double d = livingEntity.getAttributeBaseValue(attribute); -+ source.sendSuccess(Component.translatable("commands.attribute.base_value.get.success", getAttributeDescription(attribute), nmsEntity.getName(), d), false); ++ source.sendSuccess(() -> { ++ return Component.translatable("commands.attribute.base_value.get.success", getAttributeDescription(attribute), nmsEntity.getName(), d); ++ }, false); + } catch (CommandSyntaxException ex) { + sendMessage(source, ex); + } + }, null, 1L); + return 0; + // Folia end - region threading -+ } private static int getAttributeModifier(CommandSourceStack source, Entity target, Holder attribute, UUID uuid, double multiplier) throws CommandSyntaxException { @@ -12549,7 +11905,9 @@ index e846bd5db018f79c083d29f8f7b305a3d7ab45f5..b01aeb7bae3b6d3d291f76d19e880798 - throw ERROR_NO_SUCH_MODIFIER.create(target.getName(), getAttributeDescription(attribute), uuid); - } else { - double d = attributeMap.getModifierValue(attribute, uuid); -- source.sendSuccess(Component.translatable("commands.attribute.modifier.value.get.success", uuid, getAttributeDescription(attribute), target.getName(), d), false); +- source.sendSuccess(() -> { +- return Component.translatable("commands.attribute.modifier.value.get.success", uuid, getAttributeDescription(attribute), target.getName(), d); +- }, false); - return (int)(d * multiplier); - } + // Folia start - region threading @@ -12561,7 +11919,9 @@ index e846bd5db018f79c083d29f8f7b305a3d7ab45f5..b01aeb7bae3b6d3d291f76d19e880798 + throw ERROR_NO_SUCH_MODIFIER.create(nmsEntity.getName(), getAttributeDescription(attribute), uuid); + } else { + double d = attributeMap.getModifierValue(attribute, uuid); -+ source.sendSuccess(Component.translatable("commands.attribute.modifier.value.get.success", uuid, getAttributeDescription(attribute), nmsEntity.getName(), d), false); ++ source.sendSuccess(() -> { ++ return Component.translatable("commands.attribute.modifier.value.get.success", uuid, getAttributeDescription(attribute), nmsEntity.getName(), d); ++ }, false); + } + } catch (CommandSyntaxException ex) { + sendMessage(source, ex); @@ -12573,18 +11933,23 @@ index e846bd5db018f79c083d29f8f7b305a3d7ab45f5..b01aeb7bae3b6d3d291f76d19e880798 private static int setAttributeBase(CommandSourceStack source, Entity target, Holder attribute, double value) throws CommandSyntaxException { - getAttributeInstance(target, attribute).setBaseValue(value); -- source.sendSuccess(Component.translatable("commands.attribute.base_value.set.success", getAttributeDescription(attribute), target.getName(), value), false); +- source.sendSuccess(() -> { +- return Component.translatable("commands.attribute.base_value.set.success", getAttributeDescription(attribute), target.getName(), value); +- }, false); +- return 1; + // Folia start - region threading + target.getBukkitEntity().taskScheduler.schedule((LivingEntity nmsEntity) -> { + try { + getAttributeInstance(nmsEntity, attribute).setBaseValue(value); -+ source.sendSuccess(Component.translatable("commands.attribute.base_value.set.success", getAttributeDescription(attribute), nmsEntity.getName(), value), false); ++ source.sendSuccess(() -> { ++ return Component.translatable("commands.attribute.base_value.set.success", getAttributeDescription(attribute), nmsEntity.getName(), value); ++ }, false); + } catch (CommandSyntaxException ex) { + sendMessage(source, ex); + } + }, null, 1L); ++ return 0; + // Folia end - region threading - return 1; } private static int addModifier(CommandSourceStack source, Entity target, Holder attribute, UUID uuid, String name, double value, AttributeModifier.Operation operation) throws CommandSyntaxException { @@ -12594,7 +11959,9 @@ index e846bd5db018f79c083d29f8f7b305a3d7ab45f5..b01aeb7bae3b6d3d291f76d19e880798 - throw ERROR_MODIFIER_ALREADY_PRESENT.create(target.getName(), getAttributeDescription(attribute), uuid); - } else { - attributeInstance.addPermanentModifier(attributeModifier); -- source.sendSuccess(Component.translatable("commands.attribute.modifier.add.success", uuid, getAttributeDescription(attribute), target.getName()), false); +- source.sendSuccess(() -> { +- return Component.translatable("commands.attribute.modifier.add.success", uuid, getAttributeDescription(attribute), target.getName()); +- }, false); - return 1; - } + // Folia start - region threading @@ -12606,20 +11973,24 @@ index e846bd5db018f79c083d29f8f7b305a3d7ab45f5..b01aeb7bae3b6d3d291f76d19e880798 + throw ERROR_MODIFIER_ALREADY_PRESENT.create(nmsEntity.getName(), getAttributeDescription(attribute), uuid); + } else { + attributeInstance.addPermanentModifier(attributeModifier); -+ source.sendSuccess(Component.translatable("commands.attribute.modifier.add.success", uuid, getAttributeDescription(attribute), nmsEntity.getName()), false); ++ source.sendSuccess(() -> { ++ return Component.translatable("commands.attribute.modifier.add.success", uuid, getAttributeDescription(attribute), nmsEntity.getName()); ++ }, false); + } + } catch (CommandSyntaxException ex) { + sendMessage(source, ex); + } + }, null, 1L); -+ return 1; ++ return 0; + // Folia end - region threading } private static int removeModifier(CommandSourceStack source, Entity target, Holder attribute, UUID uuid) throws CommandSyntaxException { - AttributeInstance attributeInstance = getAttributeInstance(target, attribute); - if (attributeInstance.removePermanentModifier(uuid)) { -- source.sendSuccess(Component.translatable("commands.attribute.modifier.remove.success", uuid, getAttributeDescription(attribute), target.getName()), false); +- source.sendSuccess(() -> { +- return Component.translatable("commands.attribute.modifier.remove.success", uuid, getAttributeDescription(attribute), target.getName()); +- }, false); - return 1; - } else { - throw ERROR_NO_SUCH_MODIFIER.create(target.getName(), getAttributeDescription(attribute), uuid); @@ -12629,7 +12000,9 @@ index e846bd5db018f79c083d29f8f7b305a3d7ab45f5..b01aeb7bae3b6d3d291f76d19e880798 + try { + AttributeInstance attributeInstance = getAttributeInstance(nmsEntity, attribute); + if (attributeInstance.removePermanentModifier(uuid)) { -+ source.sendSuccess(Component.translatable("commands.attribute.modifier.remove.success", uuid, getAttributeDescription(attribute), nmsEntity.getName()), false); ++ source.sendSuccess(() -> { ++ return Component.translatable("commands.attribute.modifier.remove.success", uuid, getAttributeDescription(attribute), nmsEntity.getName()); ++ }, false); + } else { + throw ERROR_NO_SUCH_MODIFIER.create(nmsEntity.getName(), getAttributeDescription(attribute), uuid); + } @@ -12637,13 +12010,13 @@ index e846bd5db018f79c083d29f8f7b305a3d7ab45f5..b01aeb7bae3b6d3d291f76d19e880798 + sendMessage(source, ex); + } + }, null, 1L); -+ return 1; ++ return 0; + // Folia end - region threading } private static Component getAttributeDescription(Holder attribute) { diff --git a/src/main/java/net/minecraft/server/commands/ClearInventoryCommands.java b/src/main/java/net/minecraft/server/commands/ClearInventoryCommands.java -index 74623df731de543d3ef5832e818b10adec7b0f01..74a5e35c66e4d6aeae61733ad3ef1e51c0cfd593 100644 +index 90c061eaf40ed756dcd56bb877a617a219ea90e1..36f5d081fae92289e1b6bf012018671343dd92a0 100644 --- a/src/main/java/net/minecraft/server/commands/ClearInventoryCommands.java +++ b/src/main/java/net/minecraft/server/commands/ClearInventoryCommands.java @@ -46,9 +46,12 @@ public class ClearInventoryCommands { @@ -12661,10 +12034,10 @@ index 74623df731de543d3ef5832e818b10adec7b0f01..74a5e35c66e4d6aeae61733ad3ef1e51 if (i == 0) { diff --git a/src/main/java/net/minecraft/server/commands/DamageCommand.java b/src/main/java/net/minecraft/server/commands/DamageCommand.java -index 95d11f48d201ccf5d9f73a3fcf78782669612f75..c96355360a379849a2f2285e41d7bd7ee6a22fa8 100644 +index b8250748606d0356da0e15d432784fb3ae59438a..473d3016f604a41ec3626bb1816888d7d2b5778d 100644 --- a/src/main/java/net/minecraft/server/commands/DamageCommand.java +++ b/src/main/java/net/minecraft/server/commands/DamageCommand.java -@@ -34,12 +34,28 @@ public class DamageCommand { +@@ -34,14 +34,30 @@ public class DamageCommand { }))))))))); } @@ -12676,13 +12049,16 @@ index 95d11f48d201ccf5d9f73a3fcf78782669612f75..c96355360a379849a2f2285e41d7bd7e + private static int damage(CommandSourceStack source, Entity target, float amount, DamageSource damageSource) throws CommandSyntaxException { - if (target.hurt(damageSource, amount)) { -- source.sendSuccess(Component.translatable("commands.damage.success", amount, target.getDisplayName()), true); -- return 1; + // Folia start - region threading + target.getBukkitEntity().taskScheduler.schedule((Entity t) -> { + try { // Folia end - region threading + if (t.hurt(damageSource, amount)) { // Folia - region threading -+ source.sendSuccess(Component.translatable("commands.damage.success", amount, t.getDisplayName()), true); // Folia - region threading + source.sendSuccess(() -> { +- return Component.translatable("commands.damage.success", amount, target.getDisplayName()); +- }, true); +- return 1; ++ return Component.translatable("commands.damage.success", amount, t.getDisplayName()); ++ }, true); // Folia - region threading + return; // Folia - region threading } else { throw ERROR_INVULNERABLE.create(); @@ -12697,7 +12073,7 @@ index 95d11f48d201ccf5d9f73a3fcf78782669612f75..c96355360a379849a2f2285e41d7bd7e } } diff --git a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java -index 1bf4c5b36f53ef1e71d50d1a9af8e1410e5dff60..fd455c794fa52b565a5741b376bc394ac8dda07c 100644 +index 12d4c141a1bc72e53e18f73d7ee4d5a2467e08e1..63815d962a5f966078dca36ca30467923da816b4 100644 --- a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java +++ b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java @@ -25,12 +25,14 @@ public class DefaultGameModeCommands { @@ -12708,7 +12084,7 @@ index 1bf4c5b36f53ef1e71d50d1a9af8e1410e5dff60..fd455c794fa52b565a5741b376bc394a // Paper start - extend PlayerGameModeChangeEvent org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.empty()); if (event != null && event.isCancelled()) { - source.sendSuccess(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), false); + source.sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), false); } // Paper end + }, null, 1L); // Folia - region threading @@ -12716,7 +12092,7 @@ index 1bf4c5b36f53ef1e71d50d1a9af8e1410e5dff60..fd455c794fa52b565a5741b376bc394a } } diff --git a/src/main/java/net/minecraft/server/commands/EffectCommands.java b/src/main/java/net/minecraft/server/commands/EffectCommands.java -index 959b82e38f3442a6b19a1f9d7615b6ddab967819..6633e7a3946e06438633a3a6276c6eaa9062778c 100644 +index ebe50e2e69d346ce9266ed3f180d91ceb58008bd..227acbe74ff009c9275542af734852e3044b111b 100644 --- a/src/main/java/net/minecraft/server/commands/EffectCommands.java +++ b/src/main/java/net/minecraft/server/commands/EffectCommands.java @@ -84,7 +84,15 @@ public class EffectCommands { @@ -12736,7 +12112,7 @@ index 959b82e38f3442a6b19a1f9d7615b6ddab967819..6633e7a3946e06438633a3a6276c6eaa ++j; } } -@@ -110,8 +118,16 @@ public class EffectCommands { +@@ -114,8 +122,16 @@ public class EffectCommands { while (iterator.hasNext()) { Entity entity = (Entity) iterator.next(); @@ -12754,7 +12130,7 @@ index 959b82e38f3442a6b19a1f9d7615b6ddab967819..6633e7a3946e06438633a3a6276c6eaa } } -@@ -136,8 +152,16 @@ public class EffectCommands { +@@ -144,8 +160,16 @@ public class EffectCommands { while (iterator.hasNext()) { Entity entity = (Entity) iterator.next(); @@ -12773,10 +12149,10 @@ index 959b82e38f3442a6b19a1f9d7615b6ddab967819..6633e7a3946e06438633a3a6276c6eaa } diff --git a/src/main/java/net/minecraft/server/commands/EnchantCommand.java b/src/main/java/net/minecraft/server/commands/EnchantCommand.java -index e639c0ec642910e66b1d68ae0b9208ef58d91fce..abe7463f1e87449d65870955e58f257d4b8168c1 100644 +index 664cbce2e06fcb95d3d3d6c5302fc9119f938925..3f68902a2842f1dddc73e86ba6278e6b4b39d1dc 100644 --- a/src/main/java/net/minecraft/server/commands/EnchantCommand.java +++ b/src/main/java/net/minecraft/server/commands/EnchantCommand.java -@@ -46,43 +46,69 @@ public class EnchantCommand { +@@ -46,47 +46,73 @@ public class EnchantCommand { }))))); } @@ -12851,12 +12227,16 @@ index e639c0ec642910e66b1d68ae0b9208ef58d91fce..abe7463f1e87449d65870955e58f257d + sendMessage(source, ERROR_NOTHING_HAPPENED.create()); } else { - if (targets.size() == 1) { -- source.sendSuccess(Component.translatable("commands.enchant.success.single", enchantment2.getFullname(level), targets.iterator().next().getDisplayName()), true); + if (i == 1) { -+ source.sendSuccess(Component.translatable("commands.enchant.success.single", enchantment2.getFullname(level), possibleSingleDisplayName.get()), true); + source.sendSuccess(() -> { +- return Component.translatable("commands.enchant.success.single", enchantment2.getFullname(level), targets.iterator().next().getDisplayName()); ++ return Component.translatable("commands.enchant.success.single", enchantment2.getFullname(level), possibleSingleDisplayName.get()); + }, true); } else { -- source.sendSuccess(Component.translatable("commands.enchant.success.multiple", enchantment2.getFullname(level), targets.size()), true); -+ source.sendSuccess(Component.translatable("commands.enchant.success.multiple", enchantment2.getFullname(level), i), true); + source.sendSuccess(() -> { +- return Component.translatable("commands.enchant.success.multiple", enchantment2.getFullname(level), targets.size()); ++ return Component.translatable("commands.enchant.success.multiple", enchantment2.getFullname(level), i); + }, true); } - - return i; @@ -12866,16 +12246,18 @@ index e639c0ec642910e66b1d68ae0b9208ef58d91fce..abe7463f1e87449d65870955e58f257d + // Folia end - region threading } diff --git a/src/main/java/net/minecraft/server/commands/ExperienceCommand.java b/src/main/java/net/minecraft/server/commands/ExperienceCommand.java -index a628e3730b1c26c2e6a85c449440af0afe4c0d8d..6651376603c3fb2331ae0955343285ac7c37726f 100644 +index 24dfe8e9697331f0d7e67e90b9ca537d7ead575e..30b2dafda64a2e56d53d5d90c3087774a471f950 100644 --- a/src/main/java/net/minecraft/server/commands/ExperienceCommand.java +++ b/src/main/java/net/minecraft/server/commands/ExperienceCommand.java -@@ -46,14 +46,18 @@ public class ExperienceCommand { +@@ -46,16 +46,20 @@ public class ExperienceCommand { } private static int queryExperience(CommandSourceStack source, ServerPlayer player, ExperienceCommand.Type component) { + player.getBukkitEntity().taskScheduler.schedule((ServerPlayer p) -> { // Folia - region threading int i = component.query.applyAsInt(player); - source.sendSuccess(Component.translatable("commands.experience.query." + component.name, player.getDisplayName(), i), false); + source.sendSuccess(() -> { + return Component.translatable("commands.experience.query." + component.name, player.getDisplayName(), i); + }, false); - return i; + }, null, 1L); // Folia - region threading + return 0; @@ -12889,7 +12271,7 @@ index a628e3730b1c26c2e6a85c449440af0afe4c0d8d..6651376603c3fb2331ae0955343285ac } if (targets.size() == 1) { -@@ -69,9 +73,12 @@ public class ExperienceCommand { +@@ -75,9 +79,12 @@ public class ExperienceCommand { int i = 0; for(ServerPlayer serverPlayer : targets) { @@ -12904,7 +12286,7 @@ index a628e3730b1c26c2e6a85c449440af0afe4c0d8d..6651376603c3fb2331ae0955343285ac if (i == 0) { diff --git a/src/main/java/net/minecraft/server/commands/FillBiomeCommand.java b/src/main/java/net/minecraft/server/commands/FillBiomeCommand.java -index c8977042f26ea8f78cee3a115d2d7dec99b291ee..83a4b72b7f895d29d1e3e0b6b9f288bd5f3cb52c 100644 +index 618f524a7bba8e5c75445872538c5fd1ceff20e4..87920a58207436b328abff257369a271752df174 100644 --- a/src/main/java/net/minecraft/server/commands/FillBiomeCommand.java +++ b/src/main/java/net/minecraft/server/commands/FillBiomeCommand.java @@ -69,6 +69,12 @@ public class FillBiomeCommand { @@ -12941,10 +12323,10 @@ index c8977042f26ea8f78cee3a115d2d7dec99b291ee..83a4b72b7f895d29d1e3e0b6b9f288bd for(int k = SectionPos.blockToSectionCoord(boundingBox.minZ()); k <= SectionPos.blockToSectionCoord(boundingBox.maxZ()); ++k) { for(int l = SectionPos.blockToSectionCoord(boundingBox.minX()); l <= SectionPos.blockToSectionCoord(boundingBox.maxX()); ++l) { ChunkAccess chunkAccess = serverLevel.getChunk(l, k, ChunkStatus.FULL, false); -@@ -101,7 +118,11 @@ public class FillBiomeCommand { - - serverLevel.getChunkSource().chunkMap.resendBiomesForChunks(list); - source.sendSuccess(Component.translatable("commands.fillbiome.success.count", mutableInt.getValue(), boundingBox.minX(), boundingBox.minY(), boundingBox.minZ(), boundingBox.maxX(), boundingBox.maxY(), boundingBox.maxZ()), true); +@@ -103,7 +120,11 @@ public class FillBiomeCommand { + source.sendSuccess(() -> { + return Component.translatable("commands.fillbiome.success.count", mutableInt.getValue(), boundingBox.minX(), boundingBox.minY(), boundingBox.minZ(), boundingBox.maxX(), boundingBox.maxY(), boundingBox.maxZ()); + }, true); - return mutableInt.getValue(); + // Folia start - region threading + } catch (CommandSyntaxException ex) { @@ -12955,7 +12337,7 @@ index c8977042f26ea8f78cee3a115d2d7dec99b291ee..83a4b72b7f895d29d1e3e0b6b9f288bd } } diff --git a/src/main/java/net/minecraft/server/commands/FillCommand.java b/src/main/java/net/minecraft/server/commands/FillCommand.java -index b25b2ed0eb3c6716a7c92cb1f1066a1447d88bcf..3b55e806204cbe2df2cadcec91fbc84eed5f15b0 100644 +index e75b08126d9c42d49058fc91d68d1906fc277e8a..cb68b211bc7e5d4e4e68aa8c7d9dd95901b98e5f 100644 --- a/src/main/java/net/minecraft/server/commands/FillCommand.java +++ b/src/main/java/net/minecraft/server/commands/FillCommand.java @@ -57,6 +57,12 @@ public class FillCommand { @@ -12990,26 +12372,26 @@ index b25b2ed0eb3c6716a7c92cb1f1066a1447d88bcf..3b55e806204cbe2df2cadcec91fbc84e int k = 0; for(BlockPos blockPos : BlockPos.betweenClosed(range.minX(), range.minY(), range.minZ(), range.maxX(), range.maxY(), range.maxZ())) { -@@ -90,8 +108,13 @@ public class FillCommand { - throw ERROR_FAILED.create(); - } else { - source.sendSuccess(Component.translatable("commands.fill.success", k), true); +@@ -93,8 +111,13 @@ public class FillCommand { + source.sendSuccess(() -> { + return Component.translatable("commands.fill.success", l); + }, true); - return k; + return; // Folia - region threading } + // Folia start - region threading -+ } catch (CommandSyntaxException ex) { -+ sendMessage(source, ex); -+ } ++ } catch (CommandSyntaxException ex) { ++ sendMessage(source, ex); ++ } + }); return 0; // Folia end - region threading } } diff --git a/src/main/java/net/minecraft/server/commands/ForceLoadCommand.java b/src/main/java/net/minecraft/server/commands/ForceLoadCommand.java -index de484336165891d16220fdc0363e5283ba92b75d..494292dd2aa45771d42e5f271675f9ca267e8458 100644 +index 99250f40978aa3c45df821c3d83f80b541dbb14c..a3ea3fb7ac0f1464ea33e07978ba11c01803dcbe 100644 --- a/src/main/java/net/minecraft/server/commands/ForceLoadCommand.java +++ b/src/main/java/net/minecraft/server/commands/ForceLoadCommand.java -@@ -49,96 +49,126 @@ public class ForceLoadCommand { +@@ -49,47 +49,67 @@ public class ForceLoadCommand { })))); } @@ -13025,7 +12407,9 @@ index de484336165891d16220fdc0363e5283ba92b75d..494292dd2aa45771d42e5f271675f9ca ResourceKey resourceKey = serverLevel.dimension(); - boolean bl = serverLevel.getForcedChunks().contains(chunkPos.toLong()); - if (bl) { -- source.sendSuccess(Component.translatable("commands.forceload.query.success", chunkPos, resourceKey.location()), false); +- source.sendSuccess(() -> { +- return Component.translatable("commands.forceload.query.success", chunkPos, resourceKey.location()); +- }, false); - return 1; - } else { - throw ERROR_NOT_TICKING.create(chunkPos, resourceKey.location()); @@ -13035,7 +12419,9 @@ index de484336165891d16220fdc0363e5283ba92b75d..494292dd2aa45771d42e5f271675f9ca + try { + boolean bl = serverLevel.getForcedChunks().contains(chunkPos.toLong()); + if (bl) { -+ source.sendSuccess(Component.translatable("commands.forceload.query.success", chunkPos, resourceKey.location()), false); ++ source.sendSuccess(() -> { ++ return Component.translatable("commands.forceload.query.success", chunkPos, resourceKey.location()); ++ }, false); + return; + } else { + throw ERROR_NOT_TICKING.create(chunkPos, resourceKey.location()); @@ -13056,7 +12442,9 @@ index de484336165891d16220fdc0363e5283ba92b75d..494292dd2aa45771d42e5f271675f9ca - if (i > 0) { - String string = Joiner.on(", ").join(longSet.stream().sorted().map(ChunkPos::new).map(ChunkPos::toString).iterator()); - if (i == 1) { -- source.sendSuccess(Component.translatable("commands.forceload.list.single", resourceKey.location(), string), false); +- source.sendSuccess(() -> { +- return Component.translatable("commands.forceload.list.single", resourceKey.location(), string); +- }, false); + // Folia start - region threading + io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { + LongSet longSet = serverLevel.getForcedChunks(); @@ -13064,12 +12452,18 @@ index de484336165891d16220fdc0363e5283ba92b75d..494292dd2aa45771d42e5f271675f9ca + if (i > 0) { + String string = Joiner.on(", ").join(longSet.stream().sorted().map(ChunkPos::new).map(ChunkPos::toString).iterator()); + if (i == 1) { -+ source.sendSuccess(Component.translatable("commands.forceload.list.single", resourceKey.location(), string), false); ++ source.sendSuccess(() -> { ++ return Component.translatable("commands.forceload.list.single", resourceKey.location(), string); ++ }, false); + } else { -+ source.sendSuccess(Component.translatable("commands.forceload.list.multiple", i, resourceKey.location(), string), false); ++ source.sendSuccess(() -> { ++ return Component.translatable("commands.forceload.list.multiple", i, resourceKey.location(), string); ++ }, false); + } } else { -- source.sendSuccess(Component.translatable("commands.forceload.list.multiple", i, resourceKey.location(), string), false); +- source.sendSuccess(() -> { +- return Component.translatable("commands.forceload.list.multiple", i, resourceKey.location(), string); +- }, false); + source.sendFailure(Component.translatable("commands.forceload.added.none", resourceKey.location())); } - } else { @@ -13089,8 +12483,10 @@ index de484336165891d16220fdc0363e5283ba92b75d..494292dd2aa45771d42e5f271675f9ca LongSet longSet = serverLevel.getForcedChunks(); longSet.forEach((chunkPos) -> { serverLevel.setChunkForced(ChunkPos.getX(chunkPos), ChunkPos.getZ(chunkPos), false); - }); - source.sendSuccess(Component.translatable("commands.forceload.removed.all", resourceKey.location()), true); +@@ -97,61 +117,71 @@ public class ForceLoadCommand { + source.sendSuccess(() -> { + return Component.translatable("commands.forceload.removed.all", resourceKey.location()); + }, true); + }); // Folia - region threading return 0; } @@ -13155,25 +12551,35 @@ index de484336165891d16220fdc0363e5283ba92b75d..494292dd2aa45771d42e5f271675f9ca - } - } +- ChunkPos chunkPos2 = chunkPos; - if (r == 0) { - throw (forceLoaded ? ERROR_ALL_ADDED : ERROR_NONE_REMOVED).create(); - } else { - if (r == 1) { -- source.sendSuccess(Component.translatable("commands.forceload." + (forceLoaded ? "added" : "removed") + ".single", chunkPos, resourceKey.location()), true); +- source.sendSuccess(() -> { +- return Component.translatable("commands.forceload." + (forceLoaded ? "added" : "removed") + ".single", chunkPos2, resourceKey.location()); +- }, true); - } else { -- ChunkPos chunkPos2 = new ChunkPos(m, n); -- ChunkPos chunkPos3 = new ChunkPos(o, p); -- source.sendSuccess(Component.translatable("commands.forceload." + (forceLoaded ? "added" : "removed") + ".multiple", r, resourceKey.location(), chunkPos2, chunkPos3), true); +- ChunkPos chunkPos3 = new ChunkPos(m, n); +- ChunkPos chunkPos4 = new ChunkPos(o, p); +- source.sendSuccess(() -> { +- return Component.translatable("commands.forceload." + (forceLoaded ? "added" : "removed") + ".multiple", chunkPos2, resourceKey.location(), chunkPos3, chunkPos4); +- }, true); - } ++ ChunkPos chunkPos2 = chunkPos; + if (r == 0) { + throw (forceLoaded ? ERROR_ALL_ADDED : ERROR_NONE_REMOVED).create(); + } else { + if (r == 1) { -+ source.sendSuccess(Component.translatable("commands.forceload." + (forceLoaded ? "added" : "removed") + ".single", chunkPos, resourceKey.location()), true); ++ source.sendSuccess(() -> { ++ return Component.translatable("commands.forceload." + (forceLoaded ? "added" : "removed") + ".single", chunkPos2, resourceKey.location()); ++ }, true); + } else { -+ ChunkPos chunkPos2 = new ChunkPos(m, n); -+ ChunkPos chunkPos3 = new ChunkPos(o, p); -+ source.sendSuccess(Component.translatable("commands.forceload." + (forceLoaded ? "added" : "removed") + ".multiple", r, resourceKey.location(), chunkPos2, chunkPos3), true); ++ ChunkPos chunkPos3 = new ChunkPos(m, n); ++ ChunkPos chunkPos4 = new ChunkPos(o, p); ++ source.sendSuccess(() -> { ++ return Component.translatable("commands.forceload." + (forceLoaded ? "added" : "removed") + ".multiple", chunkPos2, resourceKey.location(), chunkPos3, chunkPos4); ++ }, true); + } - return r; @@ -13195,10 +12601,10 @@ index de484336165891d16220fdc0363e5283ba92b75d..494292dd2aa45771d42e5f271675f9ca } } diff --git a/src/main/java/net/minecraft/server/commands/GameModeCommand.java b/src/main/java/net/minecraft/server/commands/GameModeCommand.java -index 27c0aaf123c3e945eb24e8a3892bd8ac42115733..2f9f73e75b6c730a9cf327767ba1c34e34c64ed8 100644 +index 5cb15e2209d7b315904a1fc6d650ce1e75584271..4d2c88f23ba2280cba95cad41c80105a18139e73 100644 --- a/src/main/java/net/minecraft/server/commands/GameModeCommand.java +++ b/src/main/java/net/minecraft/server/commands/GameModeCommand.java -@@ -44,15 +44,18 @@ public class GameModeCommand { +@@ -48,15 +48,18 @@ public class GameModeCommand { int i = 0; for(ServerPlayer serverPlayer : targets) { @@ -13210,7 +12616,7 @@ index 27c0aaf123c3e945eb24e8a3892bd8ac42115733..2f9f73e75b6c730a9cf327767ba1c34e - ++i; + // Folia - region threading } else if (event != null && event.cancelMessage() != null) { - context.getSource().sendSuccess(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), true); + context.getSource().sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), true); // Paper end } + }, null, 1L); // Folia - region threading @@ -13219,18 +12625,18 @@ index 27c0aaf123c3e945eb24e8a3892bd8ac42115733..2f9f73e75b6c730a9cf327767ba1c34e return i; diff --git a/src/main/java/net/minecraft/server/commands/GiveCommand.java b/src/main/java/net/minecraft/server/commands/GiveCommand.java -index ee7d29d85c8b024c9b23cba8ecd4192aa7e8aa7b..9fc4e60750552dae8412ed07982ea025f934af61 100644 +index d601d287e94a59ff93b8a83a44dac02544d211df..62a03cf3b5d72765481be54362116279bda0b2f2 100644 --- a/src/main/java/net/minecraft/server/commands/GiveCommand.java +++ b/src/main/java/net/minecraft/server/commands/GiveCommand.java -@@ -55,6 +55,7 @@ public class GiveCommand { +@@ -56,6 +56,7 @@ public class GiveCommand { l -= i1; - ItemStack itemstack = item.createItemStack(i1, false); + ItemStack itemstack1 = item.createItemStack(i1, false); + entityplayer.getBukkitEntity().taskScheduler.schedule((nmsEntity) -> { // Folia - region threading - boolean flag = entityplayer.getInventory().add(itemstack); + boolean flag = entityplayer.getInventory().add(itemstack1); ItemEntity entityitem; -@@ -74,6 +75,7 @@ public class GiveCommand { +@@ -75,6 +76,7 @@ public class GiveCommand { entityitem.setTarget(entityplayer.getUUID()); } } @@ -13239,7 +12645,7 @@ index ee7d29d85c8b024c9b23cba8ecd4192aa7e8aa7b..9fc4e60750552dae8412ed07982ea025 } diff --git a/src/main/java/net/minecraft/server/commands/KillCommand.java b/src/main/java/net/minecraft/server/commands/KillCommand.java -index a6e4bd9243dab7feaed1bd968108a324d6c37ed7..4637e60292128e8c4053fb3a5fed48e53ec6553f 100644 +index 0026da50714adca207b1d3970ee808c9c09d4443..ac694041183e49ab44c5a27f3aa57ad1638298fd 100644 --- a/src/main/java/net/minecraft/server/commands/KillCommand.java +++ b/src/main/java/net/minecraft/server/commands/KillCommand.java @@ -22,7 +22,9 @@ public class KillCommand { @@ -13254,10 +12660,10 @@ index a6e4bd9243dab7feaed1bd968108a324d6c37ed7..4637e60292128e8c4053fb3a5fed48e5 if (targets.size() == 1) { diff --git a/src/main/java/net/minecraft/server/commands/PlaceCommand.java b/src/main/java/net/minecraft/server/commands/PlaceCommand.java -index c9ee0b8a0f72c21f29448c3a8fd53a7007c740f8..6ab0bbfb5e04b13082999d825e8445fbbbc184a3 100644 +index a5031c06b0215b8196c4b1b5330c318617adf3d5..597353e4bb063825089e9248860fa1c7708b7d2e 100644 --- a/src/main/java/net/minecraft/server/commands/PlaceCommand.java +++ b/src/main/java/net/minecraft/server/commands/PlaceCommand.java -@@ -83,82 +83,130 @@ public class PlaceCommand { +@@ -83,90 +83,138 @@ public class PlaceCommand { }))))))))); } @@ -13276,7 +12682,9 @@ index c9ee0b8a0f72c21f29448c3a8fd53a7007c740f8..6ab0bbfb5e04b13082999d825e8445fb - throw ERROR_FEATURE_FAILED.create(); - } else { - String string = feature.key().location().toString(); -- source.sendSuccess(Component.translatable("commands.place.feature.success", string, pos.getX(), pos.getY(), pos.getZ()), true); +- source.sendSuccess(() -> { +- return Component.translatable("commands.place.feature.success", string, pos.getX(), pos.getY(), pos.getZ()); +- }, true); - return 1; - } + // Folia start - region threading @@ -13289,7 +12697,9 @@ index c9ee0b8a0f72c21f29448c3a8fd53a7007c740f8..6ab0bbfb5e04b13082999d825e8445fb + throw ERROR_FEATURE_FAILED.create(); + } else { + String string = feature.key().location().toString(); -+ source.sendSuccess(Component.translatable("commands.place.feature.success", string, pos.getX(), pos.getY(), pos.getZ()), true); ++ source.sendSuccess(() -> { ++ return Component.translatable("commands.place.feature.success", string, pos.getX(), pos.getY(), pos.getZ()); ++ }, true); + } + } catch (CommandSyntaxException ex) { + sendMessage(source, ex); @@ -13305,7 +12715,9 @@ index c9ee0b8a0f72c21f29448c3a8fd53a7007c740f8..6ab0bbfb5e04b13082999d825e8445fb - if (!JigsawPlacement.generateJigsaw(serverLevel, structurePool, id, maxDepth, pos, false)) { - throw ERROR_JIGSAW_FAILED.create(); - } else { -- source.sendSuccess(Component.translatable("commands.place.jigsaw.success", pos.getX(), pos.getY(), pos.getZ()), true); +- source.sendSuccess(() -> { +- return Component.translatable("commands.place.jigsaw.success", pos.getX(), pos.getY(), pos.getZ()); +- }, true); - return 1; - } + // Folia start - region threading @@ -13315,7 +12727,9 @@ index c9ee0b8a0f72c21f29448c3a8fd53a7007c740f8..6ab0bbfb5e04b13082999d825e8445fb + if (!JigsawPlacement.generateJigsaw(serverLevel, structurePool, id, maxDepth, pos, false)) { + throw ERROR_JIGSAW_FAILED.create(); + } else { -+ source.sendSuccess(Component.translatable("commands.place.jigsaw.success", pos.getX(), pos.getY(), pos.getZ()), true); ++ source.sendSuccess(() -> { ++ return Component.translatable("commands.place.jigsaw.success", pos.getX(), pos.getY(), pos.getZ()); ++ }, true); + } + } catch (CommandSyntaxException ex) { + sendMessage(source, ex); @@ -13344,7 +12758,9 @@ index c9ee0b8a0f72c21f29448c3a8fd53a7007c740f8..6ab0bbfb5e04b13082999d825e8445fb - structureStart.placeInChunk(serverLevel, serverLevel.structureManager(), chunkGenerator, serverLevel.getRandom(), new BoundingBox(chunkPosx.getMinBlockX(), serverLevel.getMinBuildHeight(), chunkPosx.getMinBlockZ(), chunkPosx.getMaxBlockX(), serverLevel.getMaxBuildHeight(), chunkPosx.getMaxBlockZ()), chunkPosx); - }); - String string = structure.key().location().toString(); -- source.sendSuccess(Component.translatable("commands.place.structure.success", string, pos.getX(), pos.getY(), pos.getZ()), true); +- source.sendSuccess(() -> { +- return Component.translatable("commands.place.structure.success", string, pos.getX(), pos.getY(), pos.getZ()); +- }, true); - return 1; - } + // Folia start - region threading @@ -13365,7 +12781,9 @@ index c9ee0b8a0f72c21f29448c3a8fd53a7007c740f8..6ab0bbfb5e04b13082999d825e8445fb + structureStart.placeInChunk(serverLevel, serverLevel.structureManager(), chunkGenerator, serverLevel.getRandom(), new BoundingBox(chunkPosx.getMinBlockX(), serverLevel.getMinBuildHeight(), chunkPosx.getMinBlockZ(), chunkPosx.getMaxBlockX(), serverLevel.getMaxBuildHeight(), chunkPosx.getMaxBlockZ()), chunkPosx); + }); + String string = structure.key().location().toString(); -+ source.sendSuccess(Component.translatable("commands.place.structure.success", string, pos.getX(), pos.getY(), pos.getZ()), true); ++ source.sendSuccess(() -> { ++ return Component.translatable("commands.place.structure.success", string, pos.getX(), pos.getY(), pos.getZ()); ++ }, true); + } + } catch (CommandSyntaxException ex) { + sendMessage(source, ex); @@ -13421,13 +12839,17 @@ index c9ee0b8a0f72c21f29448c3a8fd53a7007c740f8..6ab0bbfb5e04b13082999d825e8445fb - if (!bl) { - throw ERROR_TEMPLATE_FAILED.create(); - } else { -- source.sendSuccess(Component.translatable("commands.place.template.success", id, pos.getX(), pos.getY(), pos.getZ()), true); +- source.sendSuccess(() -> { +- return Component.translatable("commands.place.template.success", id, pos.getX(), pos.getY(), pos.getZ()); +- }, true); - return 1; + boolean bl = structureTemplate.placeInWorld(serverLevel, pos, pos, structurePlaceSettings, StructureBlockEntity.createRandom((long)seed), 2); + if (!bl) { + throw ERROR_TEMPLATE_FAILED.create(); + } else { -+ source.sendSuccess(Component.translatable("commands.place.template.success", id, pos.getX(), pos.getY(), pos.getZ()), true); ++ source.sendSuccess(() -> { ++ return Component.translatable("commands.place.template.success", id, pos.getX(), pos.getY(), pos.getZ()); ++ }, true); + } + } + } catch (CommandSyntaxException ex) { @@ -13442,7 +12864,7 @@ index c9ee0b8a0f72c21f29448c3a8fd53a7007c740f8..6ab0bbfb5e04b13082999d825e8445fb private static void checkLoaded(ServerLevel world, ChunkPos pos1, ChunkPos pos2) throws CommandSyntaxException { diff --git a/src/main/java/net/minecraft/server/commands/RecipeCommand.java b/src/main/java/net/minecraft/server/commands/RecipeCommand.java -index 2a92e542e4b3e4dfb26adfc4b21490a629b79382..d3405192a705637daba66735c717d64708362bd1 100644 +index 55302ad238062a9505464d20b39cce203270a1e5..d75bef32778732ea3092791b6ca5f7453a8accb9 100644 --- a/src/main/java/net/minecraft/server/commands/RecipeCommand.java +++ b/src/main/java/net/minecraft/server/commands/RecipeCommand.java @@ -36,7 +36,12 @@ public class RecipeCommand { @@ -13459,7 +12881,7 @@ index 2a92e542e4b3e4dfb26adfc4b21490a629b79382..d3405192a705637daba66735c717d647 } if (i == 0) { -@@ -56,7 +61,12 @@ public class RecipeCommand { +@@ -60,7 +65,12 @@ public class RecipeCommand { int i = 0; for(ServerPlayer serverPlayer : targets) { @@ -13474,10 +12896,10 @@ index 2a92e542e4b3e4dfb26adfc4b21490a629b79382..d3405192a705637daba66735c717d647 if (i == 0) { diff --git a/src/main/java/net/minecraft/server/commands/SetBlockCommand.java b/src/main/java/net/minecraft/server/commands/SetBlockCommand.java -index ad435815e56ca5a8d5ea6046ee4a3ed4d3673a48..98f314064987e3c0b87e415459f6c263e75ea48d 100644 +index 342d7c12a26c6a211aae3db03ec3029c68ef650c..eda5bd9e1e1f1e1d41295874b239f503e4474cb0 100644 --- a/src/main/java/net/minecraft/server/commands/SetBlockCommand.java +++ b/src/main/java/net/minecraft/server/commands/SetBlockCommand.java -@@ -38,29 +38,45 @@ public class SetBlockCommand { +@@ -38,31 +38,47 @@ public class SetBlockCommand { }))))); } @@ -13522,13 +12944,17 @@ index ad435815e56ca5a8d5ea6046ee4a3ed4d3673a48..98f314064987e3c0b87e415459f6c263 - throw ERROR_FAILED.create(); - } else { - serverLevel.blockUpdated(pos, block.getState().getBlock()); -- source.sendSuccess(Component.translatable("commands.setblock.success", pos.getX(), pos.getY(), pos.getZ()), true); +- source.sendSuccess(() -> { +- return Component.translatable("commands.setblock.success", pos.getX(), pos.getY(), pos.getZ()); +- }, true); - return 1; + if (bl && !block.place(serverLevel, pos, 2)) { + throw ERROR_FAILED.create(); + } else { + serverLevel.blockUpdated(pos, block.getState().getBlock()); -+ source.sendSuccess(Component.translatable("commands.setblock.success", pos.getX(), pos.getY(), pos.getZ()), true); ++ source.sendSuccess(() -> { ++ return Component.translatable("commands.setblock.success", pos.getX(), pos.getY(), pos.getZ()); ++ }, true); + } + } + } catch (CommandSyntaxException ex) { @@ -13543,7 +12969,7 @@ index ad435815e56ca5a8d5ea6046ee4a3ed4d3673a48..98f314064987e3c0b87e415459f6c263 public interface Filter { diff --git a/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java b/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java -index aa701e68ed81562861eba559e61e522b934d5851..5a40cbe859ddd4f1f875752a0888ccdf7ce36953 100644 +index d797637f61bdf8a424f56fbb48e28b7c9117d604..bc371ba154b9e08a5efc213b1bbda63d1bb347cc 100644 --- a/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java +++ b/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java @@ -43,7 +43,11 @@ public class SetSpawnCommand { @@ -13560,7 +12986,7 @@ index aa701e68ed81562861eba559e61e522b934d5851..5a40cbe859ddd4f1f875752a0888ccdf } // Paper end diff --git a/src/main/java/net/minecraft/server/commands/SummonCommand.java b/src/main/java/net/minecraft/server/commands/SummonCommand.java -index 108b8ddbb724c6a2aca97bd7d5b29bea87662e70..6da8e88a64e0904a9e5f27da21559ab60659dd88 100644 +index 2eddeb8d5239bbfeefbf4d3bd363f1ad083299b6..1a4673fec0234236fceb726ca2de36ad2f556d80 100644 --- a/src/main/java/net/minecraft/server/commands/SummonCommand.java +++ b/src/main/java/net/minecraft/server/commands/SummonCommand.java @@ -63,11 +63,18 @@ public class SummonCommand { @@ -13587,19 +13013,19 @@ index 108b8ddbb724c6a2aca97bd7d5b29bea87662e70..6da8e88a64e0904a9e5f27da21559ab6 } else { return entity; diff --git a/src/main/java/net/minecraft/server/commands/TeleportCommand.java b/src/main/java/net/minecraft/server/commands/TeleportCommand.java -index 6511af08d8db1f8fc62bdb4a8f357941f524886d..8dee4f9a408194f2bbbd9028517d33504cbe188d 100644 +index 3fec07b250a8f145e30c8c41888e47d2a3c902e1..15c4fa89e1f1dbc80055f6f92904d1cc05a24dba 100644 --- a/src/main/java/net/minecraft/server/commands/TeleportCommand.java +++ b/src/main/java/net/minecraft/server/commands/TeleportCommand.java @@ -77,7 +77,7 @@ public class TeleportCommand { while (iterator.hasNext()) { Entity entity1 = (Entity) iterator.next(); -- TeleportCommand.performTeleport(source, entity1, (ServerLevel) destination.level, destination.getX(), destination.getY(), destination.getZ(), EnumSet.noneOf(RelativeMovement.class), destination.getYRot(), destination.getXRot(), (TeleportCommand.LookAt) null); +- TeleportCommand.performTeleport(source, entity1, (ServerLevel) destination.level(), destination.getX(), destination.getY(), destination.getZ(), EnumSet.noneOf(RelativeMovement.class), destination.getYRot(), destination.getXRot(), (TeleportCommand.LookAt) null); + io.papermc.paper.threadedregions.TeleportUtils.teleport(entity1, false, destination, Float.valueOf(destination.getYRot()), Float.valueOf(destination.getXRot()), Entity.TELEPORT_FLAG_LOAD_CHUNK, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND, null); // Folia - region threading } if (targets.size() == 1) { -@@ -153,6 +153,24 @@ public class TeleportCommand { +@@ -161,6 +161,24 @@ public class TeleportCommand { float f2 = Mth.wrapDegrees(yaw); float f3 = Mth.wrapDegrees(pitch); @@ -13625,10 +13051,10 @@ index 6511af08d8db1f8fc62bdb4a8f357941f524886d..8dee4f9a408194f2bbbd9028517d3350 boolean result; if (target instanceof ServerPlayer player) { diff --git a/src/main/java/net/minecraft/server/commands/TimeCommand.java b/src/main/java/net/minecraft/server/commands/TimeCommand.java -index f0a7a8df3caa2ea765bb0a87cfede71d0995d276..b666adf0d0eddea1beb59d6f7a2968b5c0296b37 100644 +index 44fcd43a466fb47d31ab05e44bafbef3c4cae63f..2989750a75b3dd244cf8f9ca78769308f982374b 100644 --- a/src/main/java/net/minecraft/server/commands/TimeCommand.java +++ b/src/main/java/net/minecraft/server/commands/TimeCommand.java -@@ -56,6 +56,7 @@ public class TimeCommand { +@@ -58,6 +58,7 @@ public class TimeCommand { while (iterator.hasNext()) { ServerLevel worldserver = (ServerLevel) iterator.next(); @@ -13636,15 +13062,15 @@ index f0a7a8df3caa2ea765bb0a87cfede71d0995d276..b666adf0d0eddea1beb59d6f7a2968b5 // CraftBukkit start TimeSkipEvent event = new TimeSkipEvent(worldserver.getWorld(), TimeSkipEvent.SkipReason.COMMAND, time - worldserver.getDayTime()); Bukkit.getPluginManager().callEvent(event); -@@ -63,6 +64,7 @@ public class TimeCommand { +@@ -65,6 +66,7 @@ public class TimeCommand { worldserver.setDayTime((long) worldserver.getDayTime() + event.getSkipAmount()); } // CraftBukkit end + }); // Folia - region threading } - source.sendSuccess(Component.translatable("commands.time.set", time), true); -@@ -75,6 +77,7 @@ public class TimeCommand { + source.sendSuccess(() -> { +@@ -79,6 +81,7 @@ public class TimeCommand { while (iterator.hasNext()) { ServerLevel worldserver = (ServerLevel) iterator.next(); @@ -13652,7 +13078,7 @@ index f0a7a8df3caa2ea765bb0a87cfede71d0995d276..b666adf0d0eddea1beb59d6f7a2968b5 // CraftBukkit start TimeSkipEvent event = new TimeSkipEvent(worldserver.getWorld(), TimeSkipEvent.SkipReason.COMMAND, time); Bukkit.getPluginManager().callEvent(event); -@@ -82,11 +85,14 @@ public class TimeCommand { +@@ -86,13 +89,16 @@ public class TimeCommand { worldserver.setDayTime(worldserver.getDayTime() + event.getSkipAmount()); } // CraftBukkit end @@ -13662,23 +13088,27 @@ index f0a7a8df3caa2ea765bb0a87cfede71d0995d276..b666adf0d0eddea1beb59d6f7a2968b5 + io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading int j = TimeCommand.getDayTime(source.getLevel()); - source.sendSuccess(Component.translatable("commands.time.set", j), true); + source.sendSuccess(() -> { + return Component.translatable("commands.time.set", j); + }, true); - return j; + }); // Folia - region threading + return 0; // Folia - region threading } } diff --git a/src/main/java/net/minecraft/server/commands/WeatherCommand.java b/src/main/java/net/minecraft/server/commands/WeatherCommand.java -index 471e9a2d8ad92c8f45856487a2919e4113ed4468..67c401bb45c1c972f1cd249547c094c854d50cc3 100644 +index 8f5714221bc32fb2c9201cbc8a0a35610977f574..c78a7e5e5c6b012756e6a1e50159dd970243cd5e 100644 --- a/src/main/java/net/minecraft/server/commands/WeatherCommand.java +++ b/src/main/java/net/minecraft/server/commands/WeatherCommand.java -@@ -35,20 +35,26 @@ public class WeatherCommand { +@@ -35,26 +35,32 @@ public class WeatherCommand { } private static int setClear(CommandSourceStack source, int duration) { + io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading source.getLevel().setWeatherParameters(getDuration(source, duration, ServerLevel.RAIN_DELAY), 0, false, false); - source.sendSuccess(Component.translatable("commands.weather.set.clear"), true); + source.sendSuccess(() -> { + return Component.translatable("commands.weather.set.clear"); + }, true); + }); // Folia - region threading return duration; } @@ -13686,7 +13116,9 @@ index 471e9a2d8ad92c8f45856487a2919e4113ed4468..67c401bb45c1c972f1cd249547c094c8 private static int setRain(CommandSourceStack source, int duration) { + io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading source.getLevel().setWeatherParameters(0, getDuration(source, duration, ServerLevel.RAIN_DURATION), true, false); - source.sendSuccess(Component.translatable("commands.weather.set.rain"), true); + source.sendSuccess(() -> { + return Component.translatable("commands.weather.set.rain"); + }, true); + }); // Folia - region threading return duration; } @@ -13694,13 +13126,15 @@ index 471e9a2d8ad92c8f45856487a2919e4113ed4468..67c401bb45c1c972f1cd249547c094c8 private static int setThunder(CommandSourceStack source, int duration) { + io.papermc.paper.threadedregions.RegionizedServer.getInstance().addTask(() -> { // Folia - region threading source.getLevel().setWeatherParameters(0, getDuration(source, duration, ServerLevel.THUNDER_DURATION), true, true); - source.sendSuccess(Component.translatable("commands.weather.set.thunder"), true); + source.sendSuccess(() -> { + return Component.translatable("commands.weather.set.thunder"); + }, true); + }); // Folia - region threading return duration; } } diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index a7e133f3495e9132a5fdae2c24f225e7b026295a..7abd4f38ae59a6019137345af960fd60a3c7adf0 100644 +index 7f0b8cdae07e7e4745a099242a4f0c5914be8667..0c497a618f4d356989f7874480c0f2cc4fff4c63 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java @@ -443,9 +443,9 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface @@ -13716,7 +13150,7 @@ index a7e133f3495e9132a5fdae2c24f225e7b026295a..7abd4f38ae59a6019137345af960fd60 } @Override -@@ -748,6 +748,12 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface +@@ -750,6 +750,12 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface @Override public String runCommand(String command) { @@ -13730,10 +13164,10 @@ index a7e133f3495e9132a5fdae2c24f225e7b026295a..7abd4f38ae59a6019137345af960fd60 this.rconConsoleSource.prepareForCommand(); this.executeBlocking(() -> { diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index a7013fdd8d82f24cadd90ea7e9ebc7c1501042d3..5ccfc95f51a899b1cd3f34af5e5bb05d902016b8 100644 +index e2202389a2c4133a183cca59c4e909fc419379ab..18f438eec03cabc1614ab807081cff6b18fb09a8 100644 --- a/src/main/java/net/minecraft/server/level/ChunkHolder.java +++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -85,18 +85,18 @@ public class ChunkHolder { +@@ -81,18 +81,18 @@ public class ChunkHolder { public void onChunkAdd() { // Paper start - optimise anyPlayerCloseEnoughForSpawning long key = io.papermc.paper.util.MCUtil.getCoordinateKey(this.pos); @@ -13756,7 +13190,7 @@ index a7013fdd8d82f24cadd90ea7e9ebc7c1501042d3..5ccfc95f51a899b1cd3f34af5e5bb05d } // Paper end - optimise checkDespawn } -@@ -108,13 +108,13 @@ public class ChunkHolder { +@@ -104,13 +104,13 @@ public class ChunkHolder { // Paper end - optimise anyPlayerCloseEnoughForSpawning // Paper start - optimise chunk tick iteration if (this.needsBroadcastChanges()) { @@ -13772,7 +13206,7 @@ index a7013fdd8d82f24cadd90ea7e9ebc7c1501042d3..5ccfc95f51a899b1cd3f34af5e5bb05d } // Paper end - optimise checkDespawn } -@@ -302,8 +302,8 @@ public class ChunkHolder { +@@ -297,8 +297,8 @@ public class ChunkHolder { } private void addToBroadcastMap() { @@ -13784,10 +13218,10 @@ index a7013fdd8d82f24cadd90ea7e9ebc7c1501042d3..5ccfc95f51a899b1cd3f34af5e5bb05d // Paper end - optimise chunk tick iteration diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8c4034781 100644 +index f0347600b2f07105ce4802273b0cfe8631ee8876..ee5dcc4dec199576eb125002e1cfdbad6d643260 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -149,21 +149,21 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -147,21 +147,21 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider private final AtomicInteger tickingGenerated; public final StructureTemplateManager structureTemplateManager; // Paper - rewrite chunk system private final String storageName; @@ -13814,18 +13248,7 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 // Paper end - optimise checkDespawn // Paper start - distance maps -@@ -177,8 +177,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // obviously this means a spawn range > 8 cannot be implemented - - // these maps are named after spigot's uses -- public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap; // this map is absent from updateMaps since it's controlled at the start of the chunkproviderserver tick -- public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerChunkTickRangeMap; -+ //public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap; // this map is absent from updateMaps since it's controlled at the start of the chunkproviderserver tick // Folia - region threading -+ //public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerChunkTickRangeMap; // Folia - region threading - // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning - // Paper start - use distance map to optimise tracker - public static boolean isLegacyTrackingEntity(Entity entity) { -@@ -187,11 +187,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -173,11 +173,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider // inlined EnumMap, TrackingRange.TrackingRangeType static final org.spigotmc.TrackingRange.TrackingRangeType[] TRACKING_RANGE_TYPES = org.spigotmc.TrackingRange.TrackingRangeType.values(); @@ -13837,8 +13260,19 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 + // Folia - region threading private int convertSpigotRangeToVanilla(final int vanilla) { - return MinecraftServer.getServer().getScaledTrackingDistance(vanilla); -@@ -203,40 +199,29 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return net.minecraft.server.MinecraftServer.getServer().getScaledTrackingDistance(vanilla); +@@ -191,8 +187,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // obviously this means a spawn range > 8 cannot be implemented + + // these maps are named after spigot's uses +- public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap; // this map is absent from updateMaps since it's controlled at the start of the chunkproviderserver tick +- public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerChunkTickRangeMap; ++ //public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap; // this map is absent from updateMaps since it's controlled at the start of the chunkproviderserver tick // Folia - region threading ++ //public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerChunkTickRangeMap; // Folia - region threading + // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning + + void addPlayerToDistanceMaps(ServerPlayer player) { +@@ -200,40 +196,29 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider int chunkX = MCUtil.getChunkCoordinate(player.getX()); int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); // Note: players need to be explicitly added to distance maps before they can be updated @@ -13888,7 +13322,7 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 // Paper end - use distance map to optimise tracker } -@@ -245,21 +230,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -242,21 +227,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); // Note: players need to be explicitly added to distance maps before they can be updated this.level.playerChunkLoader.updatePlayer(player); // Paper - replace chunk loader @@ -13914,7 +13348,7 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 } // Paper end // Paper start -@@ -297,8 +275,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -294,8 +272,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider super(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync); // Paper - rewrite chunk system this.tickingGenerated = new AtomicInteger(); @@ -13925,7 +13359,7 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 this.chunkTypeCache = new Long2ByteOpenHashMap(); this.chunkSaveCooldowns = new Long2LongOpenHashMap(); this.unloadQueue = Queues.newConcurrentLinkedQueue(); -@@ -343,96 +321,18 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -340,96 +318,18 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.setViewDistance(viewDistance); // Paper start this.dataRegionManager = new io.papermc.paper.chunk.SingleThreadChunkRegionManager(this.level, 2, (1.0 / 3.0), 1, 6, "Data", DataRegionData::new, DataRegionSectionData::new); @@ -14028,7 +13462,7 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 // Paper end - optimise checkDespawn } -@@ -460,28 +360,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -457,28 +357,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } // Paper start @@ -14058,7 +13492,7 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 // Paper end private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) { -@@ -750,6 +629,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -747,6 +626,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider // Paper start // rets true if to prevent the entity from being added public static boolean checkDupeUUID(ServerLevel level, Entity entity) { @@ -14071,7 +13505,7 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode mode = level.paperConfig().entities.spawning.duplicateUuid.mode; if (mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.WARN && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.DELETE -@@ -1014,6 +899,38 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1011,6 +896,38 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } final boolean anyPlayerCloseEnoughForSpawning(ChunkHolder playerchunk, ChunkPos chunkcoordintpair, boolean reducedRange) { @@ -14110,7 +13544,7 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 // this function is so hot that removing the map lookup call can have an order of magnitude impact on its performance // tested and confirmed via System.nanoTime() com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInRange = reducedRange ? playerchunk.playersInMobSpawnRange : playerchunk.playersInChunkTickRange; -@@ -1059,7 +976,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1056,7 +973,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider return List.of(); } else { Builder builder = ImmutableList.builder(); @@ -14119,7 +13553,7 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 while (iterator.hasNext()) { ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -@@ -1088,25 +1005,18 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1085,25 +1002,18 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } void updatePlayerStatus(ServerPlayer player, boolean added) { @@ -14150,7 +13584,7 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 this.removePlayerFromDistanceMaps(player); // Paper - distance maps } -@@ -1125,43 +1035,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1122,44 +1032,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider public void move(ServerPlayer player) { // Paper - delay this logic for the entity tracker tick, no need to duplicate it @@ -14189,25 +13623,26 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 - - int i1 = sectionposition.x(); - int j1 = sectionposition.z(); -- int k1; +- int k1 = this.viewDistance + 1; - int l1; +- int i2; + // Folia - region threading - none of this logic is relevant anymore thanks to the player chunk loader // Paper - replaced by PlayerChunkLoader -@@ -1184,9 +1058,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1182,9 +1055,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider public void addEntity(Entity entity) { org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot // Paper start - ignore and warn about illegal addEntity calls instead of crashing server -- if (!entity.valid || entity.level != this.level || this.entityMap.containsKey(entity.getId())) { -+ if (!entity.valid || entity.level != this.level || entity.tracker != null) { // Folia - region threading +- if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) { ++ if (!entity.valid || entity.level() != this.level || entity.tracker != null) { // Folia - region threading LOGGER.error("Illegal ChunkMap::addEntity for world " + this.level.getWorld().getName() - + ": " + entity + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable()); + + ": " + entity + (entity.tracker != null ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable()); return; } if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Delay adding to tracker until after list packets -@@ -1199,27 +1073,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1197,27 +1070,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider if (i != 0) { int j = entitytypes.updateInterval(); @@ -14243,7 +13678,7 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 } } -@@ -1233,16 +1105,16 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1231,16 +1102,16 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider ServerPlayer entityplayer = (ServerPlayer) entity; this.updatePlayerStatus(entityplayer, false); @@ -14267,7 +13702,7 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 if (playerchunkmap_entitytracker1 != null) { playerchunkmap_entitytracker1.broadcastRemoved(); -@@ -1252,25 +1124,18 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1250,25 +1121,18 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider // Paper start - optimised tracker private final void processTrackQueue() { @@ -14303,7 +13738,7 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 } // Paper end - optimised tracker -@@ -1281,51 +1146,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1279,51 +1143,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider return; } // Paper end - optimized tracker @@ -14357,7 +13792,7 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 if (playerchunkmap_entitytracker != null) { playerchunkmap_entitytracker.broadcast(packet); -@@ -1334,7 +1160,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1332,7 +1157,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } protected void broadcastAndSend(Entity entity, Packet packet) { @@ -14366,7 +13801,7 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 if (playerchunkmap_entitytracker != null) { playerchunkmap_entitytracker.broadcastAndSend(packet); -@@ -1521,41 +1347,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1435,41 +1260,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider this.lastSectionPos = SectionPos.of((EntityAccess) entity); } @@ -14409,7 +13844,7 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 public boolean equals(Object object) { return object instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity) object).entity.getId() == this.entity.getId() : false; -@@ -1602,6 +1394,28 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1516,6 +1307,28 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } } @@ -14438,7 +13873,7 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 public void updatePlayer(ServerPlayer player) { org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot -@@ -1617,10 +1431,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider +@@ -1532,10 +1345,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player); // CraftBukkit start - respect vanish API @@ -14456,10 +13891,10 @@ index 78ccfe201f91b9d969766a6aed4ab033a8e1218f..c8bc7db4c22f48047aa409f0bcff3ef8 if (this.seenBy.add(player.connection)) { this.serverEntity.addPairing(player); diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index 88fca8b160df6804f30ed2cf8cf1f645085434e2..341650384498eebe3f7a3315c398bec994a3195b 100644 +index ae4a4710ba07614be42cdcbf52cee04cfa08466b..e17baf04afe5140427df40450a6e0c116d1236f5 100644 --- a/src/main/java/net/minecraft/server/level/DistanceManager.java +++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -200,14 +200,14 @@ public abstract class DistanceManager { +@@ -194,14 +194,14 @@ public abstract class DistanceManager { public int getNaturalSpawnChunkCount() { // Paper start - use distance map to implement // note: this is the spawn chunk count @@ -14477,7 +13912,7 @@ index 88fca8b160df6804f30ed2cf8cf1f645085434e2..341650384498eebe3f7a3315c398bec9 } diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74673dd342 100644 +index 488a253e218409b5f0b4a872cee0928578fa7582..af35fd63b090aa3d89bc60cb9cb7694b5f502681 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -61,73 +61,42 @@ public class ServerChunkCache extends ChunkSource { @@ -14517,12 +13952,12 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 - } finally { - this.loadedChunkMapSeqLock.releaseWrite(); - } -- ++ } // Folia - region threading + - // rewrite cache if we have to - // we do this since we also cache null chunks - int cacheKey = getChunkCacheKey(chunk.locX, chunk.locZ); -+ } // Folia - region threading - +- - this.lastLoadedChunks[cacheKey] = chunk; + // Folia - region threading } @@ -14535,12 +13970,12 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 - } finally { - this.loadedChunkMapSeqLock.releaseWrite(); - } -- ++ } // Folia - region threading + - // rewrite cache if we have to - // we do this since we also cache null chunks - int cacheKey = getChunkCacheKey(chunk.locX, chunk.locZ); -+ } // Folia - region threading - +- - LevelChunk cachedChunk = this.lastLoadedChunks[cacheKey]; - if (cachedChunk != null && cachedChunk.coordinateKey == chunk.coordinateKey) { - this.lastLoadedChunks[cacheKey] = null; @@ -14566,16 +14001,7 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 } public final LevelChunk getChunkAtIfLoadedMainThreadNoCache(int x, int z) { -@@ -142,7 +111,7 @@ public class ServerChunkCache extends ChunkSource { - return (LevelChunk)this.getChunk(x, z, ChunkStatus.FULL, true); - } - -- long chunkFutureAwaitCounter; // Paper - private -> package private -+ final java.util.concurrent.atomic.AtomicLong chunkFutureAwaitCounter = new java.util.concurrent.atomic.AtomicLong(); // Paper - private -> package private // Folia - region threading - TODO MERGE INTO CHUNK SYSTEM PATCH - - public void getEntityTickingChunkAsync(int x, int z, java.util.function.Consumer onLoad) { - io.papermc.paper.chunk.system.ChunkSystem.scheduleTickingState( -@@ -293,8 +262,7 @@ public class ServerChunkCache extends ChunkSource { +@@ -164,8 +133,7 @@ public class ServerChunkCache extends ChunkSource { this.distanceManager.removeTicket(ticketType, chunkPos, ticketLevel, identifier); } @@ -14585,7 +14011,7 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 // Paper end public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory) { -@@ -368,26 +336,7 @@ public class ServerChunkCache extends ChunkSource { +@@ -239,26 +207,7 @@ public class ServerChunkCache extends ChunkSource { public LevelChunk getChunkAtIfLoadedImmediately(int x, int z) { long k = ChunkPos.asLong(x, z); @@ -14612,8 +14038,8 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 + return this.loadedChunkMap.get(k); // Folia - region threading } // Paper end - // Paper start - async chunk io -@@ -483,6 +432,7 @@ public class ServerChunkCache extends ChunkSource { + +@@ -332,6 +281,7 @@ public class ServerChunkCache extends ChunkSource { } public CompletableFuture> getChunkFuture(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) { @@ -14621,7 +14047,7 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 boolean flag1 = io.papermc.paper.util.TickThread.isTickThread(); // Paper - rewrite chunk system CompletableFuture completablefuture; -@@ -659,10 +609,11 @@ public class ServerChunkCache extends ChunkSource { +@@ -509,10 +459,11 @@ public class ServerChunkCache extends ChunkSource { } private void tickChunks() { @@ -14635,7 +14061,7 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 boolean flag = this.level.isDebug(); if (flag) { -@@ -670,9 +621,11 @@ public class ServerChunkCache extends ChunkSource { +@@ -520,9 +471,11 @@ public class ServerChunkCache extends ChunkSource { } else { // Paper start - optimize isOutisdeRange ChunkMap playerChunkMap = this.chunkMap; @@ -14649,7 +14075,7 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 continue; } -@@ -685,8 +638,9 @@ public class ServerChunkCache extends ChunkSource { +@@ -535,8 +488,9 @@ public class ServerChunkCache extends ChunkSource { com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(player.getBukkitEntity(), (byte)chunkRange); event.callEvent(); @@ -14661,7 +14087,7 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 continue; } -@@ -694,7 +648,7 @@ public class ServerChunkCache extends ChunkSource { +@@ -544,7 +498,7 @@ public class ServerChunkCache extends ChunkSource { int chunkX = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getX()); int chunkZ = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getZ()); @@ -14670,7 +14096,7 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 player.lastEntitySpawnRadiusSquared = (double)((range << 4) * (range << 4)); // used in anyPlayerCloseEnoughForSpawning player.playerNaturallySpawnedEvent = event; } -@@ -704,26 +658,19 @@ public class ServerChunkCache extends ChunkSource { +@@ -554,26 +508,19 @@ public class ServerChunkCache extends ChunkSource { gameprofilerfiller.push("pollingChunks"); int k = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); @@ -14702,7 +14128,7 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 gameprofilerfiller.popPush("filteringLoadedChunks"); // Paper - moved down this.level.timings.chunkTicks.startTiming(); // Paper -@@ -731,17 +678,17 @@ public class ServerChunkCache extends ChunkSource { +@@ -581,18 +528,18 @@ public class ServerChunkCache extends ChunkSource { // Paper - moved down gameprofilerfiller.popPush("spawnAndTick"); @@ -14711,7 +14137,8 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 // Paper - only shuffle if per-player mob spawning is disabled // Paper - moved natural spawn event up - // Paper start - optimise chunk tick iteration + + // Paper start - optimise chunk tick iteratio Iterator iterator1; - if (this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { - iterator1 = this.entityTickingChunks.iterator(); @@ -14725,7 +14152,7 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 while (iterator1.hasNext()) { shuffled.add(iterator1.next()); } -@@ -791,14 +738,19 @@ public class ServerChunkCache extends ChunkSource { +@@ -642,14 +589,19 @@ public class ServerChunkCache extends ChunkSource { // Paper start - use set of chunks requiring updates, rather than iterating every single one loaded gameprofilerfiller.popPush("broadcast"); this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing @@ -14751,7 +14178,7 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 } } } -@@ -806,8 +758,8 @@ public class ServerChunkCache extends ChunkSource { +@@ -657,8 +609,8 @@ public class ServerChunkCache extends ChunkSource { gameprofilerfiller.pop(); // Paper end - use set of chunks requiring updates, rather than iterating every single one loaded // Paper start - controlled flush for entity tracker packets @@ -14762,7 +14189,7 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 net.minecraft.server.network.ServerGamePacketListenerImpl connection = player.connection; if (connection != null) { connection.connection.disableAutomaticFlush(); -@@ -880,14 +832,19 @@ public class ServerChunkCache extends ChunkSource { +@@ -731,14 +683,19 @@ public class ServerChunkCache extends ChunkSource { @Override public void onLightUpdate(LightLayer type, SectionPos pos) { @@ -14784,7 +14211,7 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 } public void addRegionTicket(TicketType ticketType, ChunkPos pos, int radius, T argument) { -@@ -959,7 +916,8 @@ public class ServerChunkCache extends ChunkSource { +@@ -810,7 +767,8 @@ public class ServerChunkCache extends ChunkSource { @Nullable @VisibleForDebug public NaturalSpawner.SpawnState getLastSpawnState() { @@ -14794,7 +14221,7 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 } public void removeTicketsOnClosing() { -@@ -992,8 +950,43 @@ public class ServerChunkCache extends ChunkSource { +@@ -843,8 +801,43 @@ public class ServerChunkCache extends ChunkSource { return ServerChunkCache.this.mainThread; } @@ -14838,7 +14265,7 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 ServerChunkCache.this.level.getProfiler().incrementCounter("runTask"); super.doRunTask(task); } -@@ -1001,11 +994,16 @@ public class ServerChunkCache extends ChunkSource { +@@ -852,10 +845,15 @@ public class ServerChunkCache extends ChunkSource { @Override // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task public boolean pollTask() { @@ -14847,7 +14274,6 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 + throw new IllegalStateException("Polling tasks from non-owned region"); + } + // Folia end - region threading - // Paper - replace player chunk loader if (ServerChunkCache.this.runDistanceManagerUpdates()) { return true; } @@ -14857,10 +14283,10 @@ index 5c7b8041e96ede9e662dfdb5285539bf51304650..e664c568aba954e04878c06f3671ae74 } diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d916c30b5 100644 +index 2ac23779222369ace69f1e3f7fb12184865b7a43..837feb1243b7a9d96e0f68c394019920fa5546c5 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -190,35 +190,34 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -192,36 +192,35 @@ public class ServerLevel extends Level implements WorldGenLevel { public final ServerChunkCache chunkSource; private final MinecraftServer server; public final PrimaryLevelData serverLevelData; // CraftBukkit - type @@ -14887,13 +14313,14 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d + //private boolean handlingTick; // Folia - region threading private final List customSpawners; @Nullable - private final EndDragonFight dragonFight; + private EndDragonFight dragonFight; final Int2ObjectMap dragonParts; private final StructureManager structureManager; private final StructureCheck structureCheck; - private final boolean tickTime; -- public long lastMidTickExecuteFailure; // Paper - execute chunk tasks mid tick + public final boolean tickTime; // Folia - region threading + private final RandomSequences randomSequences; +- public long lastMidTickExecuteFailure; // Paper - execute chunk tasks mid tick + // Folia - region threading // CraftBukkit start @@ -14905,7 +14332,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) public static Throwable getAddToWorldStackTrace(Entity entity) { final Throwable thr = new Throwable(entity + " Added to world at " + new java.util.Date()); -@@ -254,6 +253,13 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -257,6 +256,13 @@ public class ServerLevel extends Level implements WorldGenLevel { ServerChunkCache chunkProvider = this.getChunkSource(); @@ -14919,139 +14346,14 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d for (int cx = minChunkX; cx <= maxChunkX; ++cx) { for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { if (chunkProvider.getChunkAtIfLoadedImmediately(cx, cz) == null) { -@@ -265,50 +271,64 @@ public class ServerLevel extends Level implements WorldGenLevel { - return true; +@@ -563,83 +569,60 @@ public class ServerLevel extends Level implements WorldGenLevel { } - -- public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -- java.util.function.Consumer> onLoad) { -- if (Thread.currentThread() != this.thread) { -- this.getChunkSource().mainThreadProcessor.execute(() -> { -- this.loadChunksForMoveAsync(axisalignedbb, priority, onLoad); -- }); -- return; -- } -+ // Folia start - region threading - TODO rebase -+ public final void loadChunksAsync(BlockPos pos, int radiusBlocks, -+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -+ java.util.function.Consumer> onLoad) { -+ loadChunksAsync( -+ (pos.getX() - radiusBlocks) >> 4, -+ (pos.getX() + radiusBlocks) >> 4, -+ (pos.getZ() - radiusBlocks) >> 4, -+ (pos.getZ() + radiusBlocks) >> 4, -+ priority, onLoad -+ ); -+ } -+ -+ public final void loadChunksAsync(BlockPos pos, int radiusBlocks, -+ net.minecraft.world.level.chunk.ChunkStatus chunkStatus, -+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -+ java.util.function.Consumer> onLoad) { -+ loadChunksAsync( -+ (pos.getX() - radiusBlocks) >> 4, -+ (pos.getX() + radiusBlocks) >> 4, -+ (pos.getZ() - radiusBlocks) >> 4, -+ (pos.getZ() + radiusBlocks) >> 4, -+ chunkStatus, priority, onLoad -+ ); -+ } -+ -+ public final void loadChunksAsync(int minChunkX, int maxChunkX, int minChunkZ, int maxChunkZ, -+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -+ java.util.function.Consumer> onLoad) { -+ this.loadChunksAsync(minChunkX, maxChunkX, minChunkZ, maxChunkZ, net.minecraft.world.level.chunk.ChunkStatus.FULL, priority, onLoad); -+ } -+ -+ public final void loadChunksAsync(int minChunkX, int maxChunkX, int minChunkZ, int maxChunkZ, -+ net.minecraft.world.level.chunk.ChunkStatus chunkStatus, -+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -+ java.util.function.Consumer> onLoad) { - List ret = new java.util.ArrayList<>(); -- IntArrayList ticketLevels = new IntArrayList(); -- -- int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3; -- int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3; -- -- int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3; -- int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; -- -- int minChunkX = minBlockX >> 4; -- int maxChunkX = maxBlockX >> 4; -- -- int minChunkZ = minBlockZ >> 4; -- int maxChunkZ = maxBlockZ >> 4; - - ServerChunkCache chunkProvider = this.getChunkSource(); - - int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1); -- int[] loadedChunks = new int[1]; -+ java.util.concurrent.atomic.AtomicInteger loadedChunks = new java.util.concurrent.atomic.AtomicInteger(); -+ -+ Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter.getAndIncrement()); - -- Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++); -+ int ticketLevel = 33 + net.minecraft.world.level.chunk.ChunkStatus.getDistance(chunkStatus); - - java.util.function.Consumer consumer = (net.minecraft.world.level.chunk.ChunkAccess chunk) -> { - if (chunk != null) { -- int ticketLevel = Math.max(33, chunkProvider.chunkMap.getUpdatingChunkIfPresent(chunk.getPos().toLong()).getTicketLevel()); - ret.add(chunk); -- ticketLevels.add(ticketLevel); - chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier); - } -- if (++loadedChunks[0] == requiredChunks) { -+ if (loadedChunks.incrementAndGet() == requiredChunks) { - try { - onLoad.accept(java.util.Collections.unmodifiableList(ret)); - } finally { - for (int i = 0, len = ret.size(); i < len; ++i) { - ChunkPos chunkPos = ret.get(i).getPos(); -- int ticketLevel = ticketLevels.getInt(i); - - chunkProvider.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); - chunkProvider.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, holderIdentifier); -@@ -320,11 +340,31 @@ public class ServerLevel extends Level implements WorldGenLevel { - for (int cx = minChunkX; cx <= maxChunkX; ++cx) { - for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { - io.papermc.paper.chunk.system.ChunkSystem.scheduleChunkLoad( -- this, cx, cz, net.minecraft.world.level.chunk.ChunkStatus.FULL, true, priority, consumer -+ this, cx, cz, chunkStatus, true, priority, consumer - ); - } - } - } -+ // Folia end - region threading - TODO rebase -+ -+ public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority, -+ java.util.function.Consumer> onLoad) { -+ // Folia - region threading - TODO MERGE INTO CHUNK SYSTEM PATCH -+ -+ int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3; -+ int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3; -+ -+ int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3; -+ int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; -+ -+ int minChunkX = minBlockX >> 4; -+ int maxChunkX = maxBlockX >> 4; -+ -+ int minChunkZ = minBlockZ >> 4; -+ int maxChunkZ = maxBlockZ >> 4; -+ -+ this.loadChunksAsync(minChunkX, maxChunkX, minChunkZ, maxChunkZ, priority, onLoad); // Folia - region threading - move into own function TODO rebase -+ } - - // Paper start - rewrite chunk system - public final io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler chunkTaskScheduler; -@@ -444,81 +484,16 @@ public class ServerLevel extends Level implements WorldGenLevel { // Paper end - // Paper start - optimise checkDespawn +- // Paper start - optimise checkDespawn - public final List playersAffectingSpawning = new java.util.ArrayList<>(); -+ // Folia - region threading - // Paper end - optimise checkDespawn - // Paper start - optimise get nearest players for entity AI +- // Paper end - optimise checkDespawn +- // Paper start - optimise get nearest players for entity AI - @Override - public final ServerPlayer getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions condition, @Nullable LivingEntity source, - double centerX, double centerY, double centerZ) { @@ -15082,59 +14384,8 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d - } - - return closest; -- } -- -- @Override -- public Player getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions pathfindertargetcondition, LivingEntity entityliving) { -- return this.getNearestPlayer(pathfindertargetcondition, entityliving, entityliving.getX(), entityliving.getY(), entityliving.getZ()); -- } -- -- @Override -- public Player getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions pathfindertargetcondition, -- double d0, double d1, double d2) { -- return this.getNearestPlayer(pathfindertargetcondition, null, d0, d1, d2); -- } -- -- @Override -- public List getNearbyPlayers(net.minecraft.world.entity.ai.targeting.TargetingConditions condition, LivingEntity source, AABB axisalignedbb) { -- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearby; -- double centerX = (axisalignedbb.maxX + axisalignedbb.minX) * 0.5; -- double centerZ = (axisalignedbb.maxZ + axisalignedbb.minZ) * 0.5; -- nearby = this.getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(Mth.floor(centerX) >> 4, Mth.floor(centerZ) >> 4); + // Folia - region threading - -- List ret = new java.util.ArrayList<>(); -+ // Folia - region threading - -- if (nearby == null) { -- return ret; -- } -- -- Object[] backingSet = nearby.getBackingSet(); -- -- for (int i = 0, len = backingSet.length; i < len; ++i) { -- Object _player = backingSet[i]; -- if (!(_player instanceof ServerPlayer)) { -- continue; -- } -- ServerPlayer player = (ServerPlayer)_player; -- -- if (axisalignedbb.contains(player.getX(), player.getY(), player.getZ()) && condition.test(source, player)) { -- 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.RegionizedPlayerChunkLoader playerChunkLoader = new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader(this); -@@ -563,6 +538,59 @@ public class ServerLevel extends Level implements WorldGenLevel { - }); - } - ++ + // Folia start - regionised ticking + public final io.papermc.paper.threadedregions.TickRegions tickRegions = new io.papermc.paper.threadedregions.TickRegions(); + public final io.papermc.paper.threadedregions.ThreadedRegionizer regioniser; @@ -15148,32 +14399,61 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d + this, + this.tickRegions + ); -+ } + } + public final io.papermc.paper.threadedregions.RegionizedTaskQueue.WorldRegionTaskData taskQueueRegionData = new io.papermc.paper.threadedregions.RegionizedTaskQueue.WorldRegionTaskData(this); + public static final int WORLD_INIT_NOT_CHECKED = 0; + public static final int WORLD_INIT_CHECKING = 1; + public static final int WORLD_INIT_CHECKED = 2; + public final java.util.concurrent.atomic.AtomicInteger checkInitialised = new java.util.concurrent.atomic.AtomicInteger(WORLD_INIT_NOT_CHECKED); + public ChunkPos randomSpawnSelection; -+ + +- @Override +- public Player getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions pathfindertargetcondition, LivingEntity entityliving) { +- return this.getNearestPlayer(pathfindertargetcondition, entityliving, entityliving.getX(), entityliving.getY(), entityliving.getZ()); +- } + 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<>(); -+ + +- @Override +- public Player getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions pathfindertargetcondition, +- double d0, double d1, double d2) { +- return this.getNearestPlayer(pathfindertargetcondition, null, d0, d1, d2); + public void pushPendingTeleport(final PendingTeleport teleport) { + synchronized (this.pendingTeleports) { + this.pendingTeleports.add(teleport); + } -+ } -+ + } + +- @Override +- public List getNearbyPlayers(net.minecraft.world.entity.ai.targeting.TargetingConditions condition, LivingEntity source, AABB axisalignedbb) { +- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearby; +- double centerX = (axisalignedbb.maxX + axisalignedbb.minX) * 0.5; +- double centerZ = (axisalignedbb.maxZ + axisalignedbb.minZ) * 0.5; +- nearby = this.getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(Mth.floor(centerX) >> 4, Mth.floor(centerZ) >> 4); +- +- List ret = new java.util.ArrayList<>(); +- +- if (nearby == null) { +- return ret; + public boolean removePendingTeleport(final PendingTeleport teleport) { + synchronized (this.pendingTeleports) { + return this.pendingTeleports.remove(teleport); -+ } + } + } -+ + +- Object[] backingSet = nearby.getBackingSet(); +- +- for (int i = 0, len = backingSet.length; i < len; ++i) { +- Object _player = backingSet[i]; +- if (!(_player instanceof ServerPlayer)) { +- continue; +- } +- ServerPlayer player = (ServerPlayer)_player; + public List removeAllRegionTeleports() { + final List ret = new ArrayList<>(); -+ + +- if (axisalignedbb.contains(player.getX(), player.getY(), player.getZ()) && condition.test(source, player)) { +- ret.add(player); + synchronized (this.pendingTeleports) { + for (final Iterator iterator = this.pendingTeleports.iterator(); iterator.hasNext();) { + final PendingTeleport pendingTeleport = iterator.next(); @@ -15181,17 +14461,17 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d + ret.add(pendingTeleport); + iterator.remove(); + } -+ } -+ } -+ -+ return ret; -+ } + } + } + + return ret; + } +- // Paper end - optimise get nearest players for entity AI + // 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) { - // IRegistryCustom.Dimension iregistrycustom_dimension = minecraftserver.registryAccess(); // CraftBukkit - decompile error -@@ -574,13 +602,13 @@ public class ServerLevel extends Level implements WorldGenLevel { + 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, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { +@@ -652,13 +635,13 @@ public class ServerLevel extends Level implements WorldGenLevel { this.convertable = convertable_conversionsession; this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile()); // CraftBukkit end @@ -15211,7 +14491,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d this.dragonParts = new Int2ObjectOpenHashMap(); this.tickTime = flag1; this.server = minecraftserver; -@@ -619,7 +647,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -697,7 +680,7 @@ public class ServerLevel extends Level implements WorldGenLevel { }); this.chunkSource.getGeneratorState().ensureStructuresGenerated(); this.portalForcer = new PortalForcer(this); @@ -15220,22 +14500,23 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d this.prepareWeather(); this.getWorldBorder().setAbsoluteMaxSize(minecraftserver.getAbsoluteMaxWorldSize()); this.raids = (Raids) this.getDataStorage().computeIfAbsent((nbttagcompound) -> { -@@ -647,7 +675,14 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -732,8 +715,15 @@ 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.RegionizedServer.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); -@@ -666,55 +701,31 @@ public class ServerLevel extends Level implements WorldGenLevel { ++ + // Paper start + @Override + public boolean hasChunk(int chunkX, int chunkZ) { +@@ -765,55 +755,31 @@ public class ServerLevel extends Level implements WorldGenLevel { return this.structureManager; } @@ -15275,7 +14556,8 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d - this.setDayTime(this.getDayTime() + event.getSkipAmount()); - } - } -- ++ if (region == null) this.tickSleep(); // Folia - region threading + - if (!event.isCancelled()) { - this.wakeUpAllPlayers(); - } @@ -15284,8 +14566,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d - this.resetWeatherCycle(); - } - } -+ if (region == null) this.tickSleep(); // Folia - region threading - +- - this.updateSkyBrightness(); + if (region == null) this.updateSkyBrightness(); // Folia - region threading this.tickTime(); @@ -15303,7 +14584,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d gameprofilerfiller.pop(); } timings.scheduledBlocks.stopTiming(); // Paper -@@ -731,7 +742,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -830,7 +796,7 @@ public class ServerLevel extends Level implements WorldGenLevel { timings.doSounds.startTiming(); // Spigot this.runBlockEvents(); timings.doSounds.stopTiming(); // Spigot @@ -15312,11 +14593,11 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d 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 +754,30 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -842,20 +808,30 @@ public class ServerLevel extends Level implements WorldGenLevel { gameprofilerfiller.push("entities"); timings.tickEntities.startTiming(); // Spigot if (this.dragonFight != null) { -+ if (io.papermc.paper.util.TickThread.isTickThreadFor(this, 0, 0)) { // Folia - region threading ++ if (io.papermc.paper.util.TickThread.isTickThreadFor(this, this.dragonFight.origin)) { // Folia - region threading gameprofilerfiller.push("dragonFight"); this.dragonFight.tick(); gameprofilerfiller.pop(); @@ -15344,7 +14625,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d 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 +808,31 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -886,6 +862,31 @@ public class ServerLevel extends Level implements WorldGenLevel { gameprofilerfiller.pop(); } @@ -15376,7 +14657,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d @Override public boolean shouldTickBlocksAt(long chunkPos) { // Paper start - replace player chunk loader system -@@ -797,11 +843,12 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -896,11 +897,12 @@ public class ServerLevel extends Level implements WorldGenLevel { protected void tickTime() { if (this.tickTime) { @@ -15393,7 +14674,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d this.setDayTime(this.levelData.getDayTime() + 1L); } -@@ -830,15 +877,23 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -929,15 +931,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 @@ -15420,7 +14701,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d ChunkPos chunkcoordintpair = chunk.getPos(); boolean flag = this.isRaining(); int j = chunkcoordintpair.getMinBlockX(); -@@ -846,7 +901,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -945,7 +955,7 @@ public class ServerLevel extends Level implements WorldGenLevel { ProfilerFiller gameprofilerfiller = this.getProfiler(); gameprofilerfiller.push("thunder"); @@ -15429,7 +14710,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d 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 -@@ -940,7 +995,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1040,7 +1050,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(); @@ -15438,7 +14719,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d if (index >= tickingBlocks) { continue; } -@@ -954,7 +1009,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1054,7 +1064,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); @@ -15447,7 +14728,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d // We drop the fluid tick since LAVA is ALREADY TICKED by the above method (See LiquidBlock). // TODO CHECK ON UPDATE (ping the Canadian) } -@@ -1008,7 +1063,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1108,7 +1118,7 @@ public class ServerLevel extends Level implements WorldGenLevel { } public boolean isHandlingTick() { @@ -15456,7 +14737,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d } public boolean canSleepThroughNights() { -@@ -1040,6 +1095,14 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1140,6 +1150,14 @@ public class ServerLevel extends Level implements WorldGenLevel { } public void updateSleepingPlayerList() { @@ -15471,7 +14752,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d if (!this.players.isEmpty() && this.sleepStatus.update(this.players)) { this.announceSleepStatus(); } -@@ -1051,7 +1114,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1151,7 +1169,7 @@ public class ServerLevel extends Level implements WorldGenLevel { return this.server.getScoreboard(); } @@ -15480,16 +14761,16 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d boolean flag = this.isRaining(); if (this.dimensionType().hasSkyLight()) { -@@ -1137,23 +1200,24 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1237,23 +1255,24 @@ public class ServerLevel extends Level implements WorldGenLevel { this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.THUNDER_LEVEL_CHANGE, this.thunderLevel)); } // */ - for (int idx = 0; idx < this.players.size(); ++idx) { -- if (((ServerPlayer) this.players.get(idx)).level == this) { +- if (((ServerPlayer) this.players.get(idx)).level() == this) { - ((ServerPlayer) this.players.get(idx)).tickWeather(); + ServerPlayer[] players = this.players.toArray(new ServerPlayer[0]); // Folia - region threading + for (ServerPlayer player : players) { // Folia - region threading -+ if (player.level == this) { // Folia - region threading ++ if (player.level() == this) { // Folia - region threading + player.tickWeather(); // Folia - region threading } } @@ -15497,24 +14778,24 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d if (flag != this.isRaining()) { // Only send weather packets to those affected - for (int idx = 0; idx < this.players.size(); ++idx) { -- if (((ServerPlayer) this.players.get(idx)).level == this) { +- if (((ServerPlayer) this.players.get(idx)).level() == this) { - ((ServerPlayer) this.players.get(idx)).setPlayerWeather((!flag ? WeatherType.DOWNFALL : WeatherType.CLEAR), false); + for (ServerPlayer player : players) { // Folia - region threading -+ if (player.level == this) { // Folia - region threading ++ if (player.level() == this) { // Folia - region threading + player.setPlayerWeather((!flag ? WeatherType.DOWNFALL : WeatherType.CLEAR), false); // Folia - region threading } } } - for (int idx = 0; idx < this.players.size(); ++idx) { -- if (((ServerPlayer) this.players.get(idx)).level == this) { +- if (((ServerPlayer) this.players.get(idx)).level() == this) { - ((ServerPlayer) this.players.get(idx)).updateWeather(this.oRainLevel, this.rainLevel, this.oThunderLevel, this.thunderLevel); + for (ServerPlayer player : players) { // Folia - region threading -+ if (player.level == this) { // Folia - region threading ++ if (player.level() == this) { // Folia - region threading + player.updateWeather(this.oRainLevel, this.rainLevel, this.oThunderLevel, this.thunderLevel); // Folia - region threading } } // CraftBukkit end -@@ -1217,7 +1281,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1317,7 +1336,7 @@ public class ServerLevel extends Level implements WorldGenLevel { public void tickNonPassenger(Entity entity) { // Paper start - log detailed entity tick information @@ -15523,7 +14804,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d try { if (currentlyTickingEntity.get() == null) { currentlyTickingEntity.lazySet(entity); -@@ -1250,7 +1314,16 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1350,7 +1369,16 @@ public class ServerLevel extends Level implements WorldGenLevel { if (isActive) { // Paper - EAR 2 TimingHistory.activatedEntityTicks++; entity.tick(); @@ -15541,7 +14822,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d } else { entity.inactiveTick(); } // Paper - EAR 2 this.getProfiler().pop(); } finally { timer.stopTiming(); } // Paper - timings -@@ -1273,7 +1346,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1373,7 +1401,7 @@ public class ServerLevel extends Level implements WorldGenLevel { private void tickPassenger(Entity vehicle, Entity passenger) { if (!passenger.isRemoved() && passenger.getVehicle() == vehicle) { @@ -15550,7 +14831,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d // 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 -@@ -1290,7 +1363,16 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1390,7 +1418,16 @@ public class ServerLevel extends Level implements WorldGenLevel { // Paper start - EAR 2 if (isActive) { passenger.rideTick(); @@ -15568,7 +14849,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d } else { passenger.setDeltaMovement(Vec3.ZERO); passenger.inactiveTick(); -@@ -1378,7 +1460,15 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1478,7 +1515,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 @@ -15584,7 +14865,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d // CraftBukkit start - moved from MinecraftServer.saveChunks ServerLevel worldserver1 = this; -@@ -1386,12 +1476,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1486,12 +1531,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 @@ -15598,7 +14879,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d this.getChunkSource().getDataStorage().save(); } -@@ -1446,6 +1531,19 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1546,6 +1586,19 @@ public class ServerLevel extends Level implements WorldGenLevel { return list; } @@ -15618,7 +14899,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d @Nullable public ServerPlayer getRandomPlayer() { List list = this.getPlayers(LivingEntity::isAlive); -@@ -1547,8 +1645,8 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1647,8 +1700,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 @@ -15629,7 +14910,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d return true; } // Paper end -@@ -1692,7 +1790,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1792,7 +1845,7 @@ public class ServerLevel extends Level implements WorldGenLevel { @Override public void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) { @@ -15638,7 +14919,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d String s = "recursive call to sendBlockUpdated"; Util.logAndPauseIfInIde("recursive call to sendBlockUpdated", new IllegalStateException("recursive call to sendBlockUpdated")); -@@ -1705,7 +1803,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1805,7 +1858,7 @@ public class ServerLevel extends Level implements WorldGenLevel { if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) { List list = new ObjectArrayList(); @@ -15647,7 +14928,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d while (iterator.hasNext()) { // CraftBukkit start - fix SPIGOT-6362 -@@ -1728,7 +1826,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1828,7 +1881,7 @@ public class ServerLevel extends Level implements WorldGenLevel { } try { @@ -15656,7 +14937,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d iterator = list.iterator(); while (iterator.hasNext()) { -@@ -1737,7 +1835,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1837,7 +1890,7 @@ public class ServerLevel extends Level implements WorldGenLevel { navigationabstract1.recomputePath(); } } finally { @@ -15665,7 +14946,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d } } -@@ -1746,23 +1844,23 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1846,23 +1899,23 @@ public class ServerLevel extends Level implements WorldGenLevel { @Override public void updateNeighborsAt(BlockPos pos, Block sourceBlock) { @@ -15694,7 +14975,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d } @Override -@@ -1793,7 +1891,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1893,7 +1946,7 @@ public class ServerLevel extends Level implements WorldGenLevel { explosion.clearToBlow(); } @@ -15703,7 +14984,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d while (iterator.hasNext()) { ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -@@ -1808,25 +1906,28 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1908,25 +1961,28 @@ public class ServerLevel extends Level implements WorldGenLevel { @Override public void blockEvent(BlockPos pos, Block block, int type, int data) { @@ -15738,7 +15019,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d } private boolean doBlockEvent(BlockEventData event) { -@@ -1837,12 +1938,12 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1937,12 +1993,12 @@ public class ServerLevel extends Level implements WorldGenLevel { @Override public LevelTicks getBlockTicks() { @@ -15753,7 +15034,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d } @Nonnull -@@ -1866,7 +1967,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1966,7 +2022,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 @@ -15762,7 +15043,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d } 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 -@@ -1919,7 +2020,14 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2019,7 +2075,14 @@ public class ServerLevel extends Level implements WorldGenLevel { public Entity getEntityOrPart(int id) { Entity entity = (Entity) this.getEntities().get(id); @@ -15778,7 +15059,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d } @Nullable -@@ -1927,6 +2035,61 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2027,6 +2090,61 @@ public class ServerLevel extends Level implements WorldGenLevel { return (Entity) this.getEntities().get(uuid); } @@ -15840,7 +15121,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d @Nullable public BlockPos findNearestMapStructure(TagKey structureTag, BlockPos pos, int radius, boolean skipReferencedStructures) { if (!this.serverLevelData.worldGenOptions().generateStructures()) { // CraftBukkit -@@ -2079,6 +2242,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2179,6 +2297,7 @@ public class ServerLevel extends Level implements WorldGenLevel { } public boolean setChunkForced(int x, int z, boolean forced) { @@ -15848,7 +15129,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d ForcedChunksSavedData forcedchunk = (ForcedChunksSavedData) this.getDataStorage().computeIfAbsent(ForcedChunksSavedData::load, ForcedChunksSavedData::new, "chunks"); ChunkPos chunkcoordintpair = new ChunkPos(x, z); long k = chunkcoordintpair.toLong(); -@@ -2087,7 +2251,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2187,7 +2306,7 @@ public class ServerLevel extends Level implements WorldGenLevel { if (forced) { flag1 = forcedchunk.getChunks().add(k); if (flag1) { @@ -15857,7 +15138,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d } } else { flag1 = forcedchunk.getChunks().remove(k); -@@ -2115,13 +2279,18 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2215,13 +2334,18 @@ public class ServerLevel extends Level implements WorldGenLevel { BlockPos blockposition1 = pos.immutable(); optional.ifPresent((holder) -> { @@ -15879,7 +15160,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d // Paper start if (optional.isEmpty() && this.getPoiManager().exists(blockposition1, poiType -> true)) { this.getPoiManager().remove(blockposition1); -@@ -2129,7 +2298,12 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2229,7 +2353,12 @@ public class ServerLevel extends Level implements WorldGenLevel { // Paper end this.getPoiManager().add(blockposition1, holder); DebugPackets.sendPoiAddedPacket(this, blockposition1); @@ -15893,7 +15174,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d }); } } -@@ -2176,7 +2350,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2276,7 +2405,7 @@ public class ServerLevel extends Level implements WorldGenLevel { BufferedWriter bufferedwriter = Files.newBufferedWriter(path.resolve("stats.txt")); try { @@ -15902,7 +15183,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d NaturalSpawner.SpawnState spawnercreature_d = this.getChunkSource().getLastSpawnState(); if (spawnercreature_d != null) { -@@ -2190,7 +2364,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2290,7 +2419,7 @@ public class ServerLevel extends Level implements WorldGenLevel { } bufferedwriter.write(String.format(Locale.ROOT, "entities: %s\n", this.entityLookup.getDebugInfo())); // Paper - rewrite chunk system @@ -15911,7 +15192,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d 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"); -@@ -2336,7 +2510,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2436,7 +2565,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); @@ -15920,7 +15201,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d while (iterator.hasNext()) { TickingBlockEntity tickingblockentity = (TickingBlockEntity) iterator.next(); -@@ -2349,7 +2523,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2449,7 +2578,7 @@ public class ServerLevel extends Level implements WorldGenLevel { @VisibleForTesting public void clearBlockEvents(BoundingBox box) { @@ -15929,7 +15210,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d return box.isInside(blockactiondata.pos()); }); } -@@ -2358,7 +2532,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2458,7 +2587,7 @@ public class ServerLevel extends Level implements WorldGenLevel { public void blockUpdated(BlockPos pos, Block block) { if (!this.isDebug()) { // CraftBukkit start @@ -15938,7 +15219,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d return; } // CraftBukkit end -@@ -2401,9 +2575,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2501,9 +2630,7 @@ public class ServerLevel extends Level implements WorldGenLevel { @VisibleForTesting public String getWatchdogStats() { @@ -15949,7 +15230,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d } private static String getTypeCount(Iterable items, Function classifier) { -@@ -2436,6 +2608,12 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2536,6 +2663,12 @@ public class ServerLevel extends Level implements WorldGenLevel { public static void makeObsidianPlatform(ServerLevel worldserver, Entity entity) { // CraftBukkit end BlockPos blockposition = ServerLevel.END_SPAWN_POINT; @@ -15962,7 +15243,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d int i = blockposition.getX(); int j = blockposition.getY() - 2; int k = blockposition.getZ(); -@@ -2448,11 +2626,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2548,11 +2681,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); }); @@ -15975,7 +15256,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d blockList.updateList(); } // CraftBukkit end -@@ -2473,13 +2647,14 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2573,13 +2702,14 @@ public class ServerLevel extends Level implements WorldGenLevel { } public void startTickingChunk(LevelChunk chunk) { @@ -15994,7 +15275,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d } @Override -@@ -2501,7 +2676,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2601,7 +2731,7 @@ public class ServerLevel extends Level implements WorldGenLevel { // Paper end - rewrite chunk system } @@ -16003,7 +15284,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d // 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 -@@ -2549,16 +2724,16 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2657,16 +2787,16 @@ public class ServerLevel extends Level implements WorldGenLevel { public void onCreated(Entity entity) {} public void onDestroyed(Entity entity) { @@ -16023,7 +15304,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d // 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; -@@ -2586,7 +2761,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2694,7 +2824,7 @@ public class ServerLevel extends Level implements WorldGenLevel { Util.logAndPauseIfInIde("onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")); } @@ -16032,7 +15313,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d } if (entity instanceof EnderDragon) { -@@ -2597,7 +2772,9 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2705,7 +2835,9 @@ public class ServerLevel extends Level implements WorldGenLevel { for (int j = 0; j < i; ++j) { EnderDragonPart entitycomplexpart = aentitycomplexpart[j]; @@ -16042,7 +15323,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d } } -@@ -2623,11 +2800,18 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2731,11 +2863,18 @@ public class ServerLevel extends Level implements WorldGenLevel { { com.google.common.collect.Streams.stream( ServerLevel.this.getServer().getAllLevels() ).map( ServerLevel::getDataStorage ).forEach( (worldData) -> { @@ -16062,7 +15343,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d map.carriedByPlayers.remove( (Player) entity ); for ( Iterator iter = (Iterator) map.carriedBy.iterator(); iter.hasNext(); ) { -@@ -2637,6 +2821,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2745,6 +2884,7 @@ public class ServerLevel extends Level implements WorldGenLevel { iter.remove(); } } @@ -16070,7 +15351,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d } } } ); -@@ -2671,7 +2856,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2779,7 +2919,7 @@ public class ServerLevel extends Level implements WorldGenLevel { Util.logAndPauseIfInIde("onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")); } @@ -16079,7 +15360,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d } if (entity instanceof EnderDragon) { -@@ -2682,13 +2867,16 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2790,13 +2930,16 @@ public class ServerLevel extends Level implements WorldGenLevel { for (int j = 0; j < i; ++j) { EnderDragonPart entitycomplexpart = aentitycomplexpart[j]; @@ -16097,7 +15378,7 @@ index 7b7e2edbcb6c8eb45029e6cbf4fc9d2e9052b668..6db245339eaa2c72253231d1e71c363d 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 37dfb5f2e56d8d7bf114dac6be82ccf0fbfc8a09..c4970eefb48baf24cc51ba3cb27997984be872db 100644 +index 5fad40fa88f697108e42461c41012d5964ed7d75..6d3d8fd7d8e21fe9ba47574b764b04f627887d56 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -190,7 +190,7 @@ import org.bukkit.inventory.MainHand; @@ -16122,7 +15403,7 @@ index 37dfb5f2e56d8d7bf114dac6be82ccf0fbfc8a09..c4970eefb48baf24cc51ba3cb2799798 // CraftBukkit start public String displayName; -@@ -417,7 +413,7 @@ public class ServerPlayer extends Player { +@@ -416,7 +412,7 @@ public class ServerPlayer extends Player { this.adventure$displayName = net.kyori.adventure.text.Component.text(this.getScoreboardName()); // Paper this.bukkitPickUpLoot = true; this.maxHealthCache = this.getMaxHealth(); @@ -16131,7 +15412,7 @@ index 37dfb5f2e56d8d7bf114dac6be82ccf0fbfc8a09..c4970eefb48baf24cc51ba3cb2799798 } // Yes, this doesn't match Vanilla, but it's the best we can do for now. -@@ -459,11 +455,11 @@ public class ServerPlayer extends Player { +@@ -458,11 +454,11 @@ public class ServerPlayer extends Player { } // CraftBukkit end @@ -16146,7 +15427,7 @@ index 37dfb5f2e56d8d7bf114dac6be82ccf0fbfc8a09..c4970eefb48baf24cc51ba3cb2799798 int j = Mth.floor(world.getWorldBorder().getDistanceToBorder((double) blockposition.getX(), (double) blockposition.getZ())); if (j < i) { -@@ -477,33 +473,76 @@ public class ServerPlayer extends Player { +@@ -476,33 +472,76 @@ public class ServerPlayer extends Player { long k = (long) (i * 2 + 1); long l = k * k; int i1 = l > 2147483647L ? Integer.MAX_VALUE : (int) l; @@ -16242,7 +15523,7 @@ index 37dfb5f2e56d8d7bf114dac6be82ccf0fbfc8a09..c4970eefb48baf24cc51ba3cb2799798 return horizontalSpawnArea <= 16 ? horizontalSpawnArea - 1 : 17; } -@@ -1162,6 +1201,343 @@ public class ServerPlayer extends Player { +@@ -1161,6 +1200,343 @@ public class ServerPlayer extends Player { } } @@ -16586,7 +15867,7 @@ index 37dfb5f2e56d8d7bf114dac6be82ccf0fbfc8a09..c4970eefb48baf24cc51ba3cb2799798 @Nullable @Override public Entity changeDimension(ServerLevel destination) { -@@ -1171,6 +1547,11 @@ public class ServerPlayer extends Player { +@@ -1170,6 +1546,11 @@ public class ServerPlayer extends Player { @Nullable public Entity changeDimension(ServerLevel worldserver, PlayerTeleportEvent.TeleportCause cause) { @@ -16598,7 +15879,7 @@ index 37dfb5f2e56d8d7bf114dac6be82ccf0fbfc8a09..c4970eefb48baf24cc51ba3cb2799798 // CraftBukkit end if (this.isSleeping()) return this; // CraftBukkit - SPIGOT-3154 // this.isChangingDimension = true; // CraftBukkit - Moved down and into PlayerList#changeDimension -@@ -2111,6 +2492,12 @@ public class ServerPlayer extends Player { +@@ -2114,6 +2495,12 @@ public class ServerPlayer extends Player { public void setCamera(@Nullable Entity entity) { Entity entity1 = this.getCamera(); @@ -16611,7 +15892,7 @@ index 37dfb5f2e56d8d7bf114dac6be82ccf0fbfc8a09..c4970eefb48baf24cc51ba3cb2799798 this.camera = (Entity) (entity == null ? this : entity); if (entity1 != this.camera) { // Paper start - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity Event -@@ -2304,7 +2691,7 @@ public class ServerPlayer extends Player { +@@ -2307,7 +2694,7 @@ public class ServerPlayer extends Player { } public void untrackChunk(ChunkPos chunkPos) { @@ -16620,7 +15901,7 @@ index 37dfb5f2e56d8d7bf114dac6be82ccf0fbfc8a09..c4970eefb48baf24cc51ba3cb2799798 this.connection.send(new ClientboundForgetLevelChunkPacket(chunkPos.x, chunkPos.z)); // Paper start if(io.papermc.paper.event.packet.PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0){ -@@ -2583,7 +2970,7 @@ public class ServerPlayer extends Player { +@@ -2626,7 +3013,7 @@ public class ServerPlayer extends Player { this.experienceLevel = this.newLevel; this.totalExperience = this.newTotalExp; this.experienceProgress = 0; @@ -16630,10 +15911,10 @@ index 37dfb5f2e56d8d7bf114dac6be82ccf0fbfc8a09..c4970eefb48baf24cc51ba3cb2799798 this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.DEATH); this.effectsDirty = true; diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 1d33c02088c150189d7f4b0aa27f6a1de96b11cf..59a9a9633ac924c606564b75298fae22f1ffd4ec 100644 +index bb7ad618fad7f11a02d7e088e2c05bfffaf0b41d..d9e622964b415b07caaa714d7d23649dcc62c677 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -123,11 +123,11 @@ public class ServerPlayerGameMode { +@@ -124,11 +124,11 @@ public class ServerPlayerGameMode { } public void tick() { @@ -16647,7 +15928,7 @@ index 1d33c02088c150189d7f4b0aa27f6a1de96b11cf..59a9a9633ac924c606564b75298fae22 if (iblockdata == null || iblockdata.isAir()) { // Paper this.hasDelayedDestroy = false; } else { -@@ -140,7 +140,7 @@ public class ServerPlayerGameMode { +@@ -141,7 +141,7 @@ public class ServerPlayerGameMode { } } else if (this.isDestroyingBlock) { // Paper start - don't want to do same logic as above, return instead @@ -16656,7 +15937,7 @@ index 1d33c02088c150189d7f4b0aa27f6a1de96b11cf..59a9a9633ac924c606564b75298fae22 if (iblockdata == null) { this.isDestroyingBlock = false; return; -@@ -416,7 +416,7 @@ public class ServerPlayerGameMode { +@@ -415,7 +415,7 @@ public class ServerPlayerGameMode { } else { // CraftBukkit start org.bukkit.block.BlockState state = bblock.getState(); @@ -16665,7 +15946,7 @@ index 1d33c02088c150189d7f4b0aa27f6a1de96b11cf..59a9a9633ac924c606564b75298fae22 // CraftBukkit end block.playerWillDestroy(this.level, pos, iblockdata, this.player); boolean flag = this.level.removeBlock(pos, false); -@@ -444,8 +444,8 @@ public class ServerPlayerGameMode { +@@ -443,8 +443,8 @@ public class ServerPlayerGameMode { // return true; // CraftBukkit } // CraftBukkit start @@ -16677,16 +15958,16 @@ index 1d33c02088c150189d7f4b0aa27f6a1de96b11cf..59a9a9633ac924c606564b75298fae22 org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, itemsToDrop); // Paper - use stored ref } diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -index 660693c6dc0ef86f4013df980b6d0c11c03e46cd..b64fc5adda3a9ab793a074d08df822754bd8d186 100644 +index b4be02ec4bb77059f79d3e4d6a6f1ee4843a01f9..b3d9133a569c0257c3ad2728f023a883fa730fad 100644 --- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java +++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -@@ -98,10 +98,15 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - this.chunkMap.level.chunkTaskScheduler.lightExecutor.queueRunnable(() -> { // Paper - rewrite chunk system +@@ -97,10 +97,15 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl + this.chunkMap.level.chunkTaskScheduler.radiusAwareScheduler.queueInfiniteRadiusTask(() -> { // Paper - rewrite chunk system this.theLightEngine.relightChunks(chunks, (ChunkPos chunkPos) -> { chunkLightCallback.accept(chunkPos); - ((java.util.concurrent.Executor)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().mainThreadProcessor).execute(() -> { + Runnable run = () -> { // Folia - region threading - ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong()).broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunkPos, ThreadedLevelLightEngine.this, null, null, true), false); + ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong()).broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunkPos, ThreadedLevelLightEngine.this, null, null), false); ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().removeTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, io.papermc.paper.util.MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), ticketIds.get(chunkPos)); - }); + }; // Folia - region threading @@ -16698,7 +15979,7 @@ index 660693c6dc0ef86f4013df980b6d0c11c03e46cd..b64fc5adda3a9ab793a074d08df82275 }, onComplete); }); this.tryScheduleUpdate(); -@@ -109,7 +114,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl +@@ -108,7 +113,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl return totalChunks; } @@ -16707,7 +15988,7 @@ index 660693c6dc0ef86f4013df980b6d0c11c03e46cd..b64fc5adda3a9ab793a074d08df82275 private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ, final Supplier> runnable) { final ServerLevel world = (ServerLevel)this.theLightEngine.getWorld(); -@@ -128,11 +133,16 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl +@@ -127,11 +132,16 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl return; } @@ -16727,7 +16008,7 @@ index 660693c6dc0ef86f4013df980b6d0c11c03e46cd..b64fc5adda3a9ab793a074d08df82275 return; } -@@ -145,22 +155,28 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl +@@ -144,22 +154,28 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl return; } @@ -16768,12 +16049,12 @@ index 660693c6dc0ef86f4013df980b6d0c11c03e46cd..b64fc5adda3a9ab793a074d08df82275 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..8bede935166cfe0defbc7bb1e959bd91f2cd1c6e 100644 +index 658e63ebde81dc14c8ab5850fb246dc0aab25dea..a0b2bc13795daa5a4cc2366ec796709b3cb6974f 100644 --- a/src/main/java/net/minecraft/server/level/TicketType.java +++ b/src/main/java/net/minecraft/server/level/TicketType.java -@@ -35,6 +35,15 @@ 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); +@@ -37,6 +37,15 @@ public class TicketType { + public static final TicketType NON_FULL_SYNC_LOAD = create("non_full_sync_load", Long::compareTo); + public static final TicketType DELAY_UNLOAD = create("delay_unload", Comparator.comparingLong(ChunkPos::toLong), 1); // Paper end - rewrite chunk system + // Folia start - region threading + public static final TicketType LOGIN = create("login", (u1, u2) -> 0, 20); @@ -16834,7 +16115,7 @@ index 44d99e89226adb6234b9405f25ac9dab9bd84297..072634e26d32ca0b3438a5d3a03be367 Collections.shuffle( this.connections ); } diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085aead4d3eb 100644 +index 1288f651fa83d5ab99a88858f52a6d3212284df0..502c64391db8d0dde3dce4edb976635cb9d0522a 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -325,10 +325,10 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic @@ -16971,7 +16252,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a } private CompletableFuture filterTextPacket(T text, BiFunction> filterer) { -@@ -620,9 +633,10 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -619,9 +632,10 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic // Paper end - fix large move vectors killing the server // CraftBukkit start - handle custom speeds and skipped ticks @@ -16984,7 +16265,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a ++this.receivedMovePacketCount; int i = this.receivedMovePacketCount - this.knownMovePacketCount; -@@ -760,7 +774,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -767,7 +781,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic // there to avoid any 'Moved wrongly' or 'Moved too quickly' errors. // We only do this if the Event was not cancelled. if (!oldTo.equals(event.getTo()) && !event.isCancelled()) { @@ -16993,8 +16274,8 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a return; } -@@ -876,13 +890,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic - // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); // Paper - run this async +@@ -883,13 +897,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic + // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - run this async // CraftBukkit start if (this.chatSpamTickCount.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.tabSpamLimit && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { // Paper start - split and make configurable - server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause @@ -17004,12 +16285,12 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a // Paper start String str = packet.getCommand(); int index = -1; if (str.length() > 64 && ((index = str.indexOf(' ')) == -1 || index >= 64)) { -- server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause -+ this.disconnect(Component.translatable("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause // Folia - region threading +- server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", new Object[0]))); // Paper ++ this.disconnect(Component.translatable("disconnect.spam", new Object[0])); // Paper // Folia - region threading return; } // Paper end -@@ -907,7 +921,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -914,7 +928,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic if (!event.isHandled()) { if (!event.isCancelled()) { @@ -17018,7 +16299,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { -@@ -918,7 +932,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -925,7 +939,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic this.connection.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestEvent.getSuggestions())); // Paper end - Brigadier API }); @@ -17027,7 +16308,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a } } else if (!completions.isEmpty()) { final com.mojang.brigadier.suggestion.SuggestionsBuilder builder0 = new com.mojang.brigadier.suggestion.SuggestionsBuilder(command, stringreader.getTotalLength()); -@@ -1227,7 +1241,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -1230,7 +1244,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; if (byteLength > 256 * 4) { ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with with a page too large!"); @@ -17036,7 +16317,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a return; } byteTotal += byteLength; -@@ -1250,17 +1264,17 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -1253,17 +1267,17 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic if (byteTotal > byteAllowed) { ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size()); @@ -17058,7 +16339,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a // CraftBukkit end int i = packet.getSlot(); -@@ -1280,7 +1294,19 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -1283,7 +1297,19 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic this.updateBookContents(list1, i); }; @@ -17079,7 +16360,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a } } -@@ -1447,9 +1473,10 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -1449,9 +1475,10 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic int i = this.receivedMovePacketCount - this.knownMovePacketCount; // CraftBukkit start - handle custom speeds and skipped ticks @@ -17092,7 +16373,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a if (i > Math.max(this.allowedPlayerTicks, 5)) { ServerGamePacketListenerImpl.LOGGER.debug("{} is sending move packets too frequently ({} packets since last tick)", this.player.getName().getString(), i); -@@ -1614,7 +1641,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -1613,7 +1640,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic // there to avoid any 'Moved wrongly' or 'Moved too quickly' errors. // We only do this if the Event was not cancelled. if (!oldTo.equals(event.getTo()) && !event.isCancelled()) { @@ -17101,7 +16382,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a return; } -@@ -1829,9 +1856,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -1827,9 +1854,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic if (!this.player.isSpectator()) { // limit how quickly items can be dropped // If the ticks aren't the same then the count starts from 0 and we update the lastDropTick. @@ -17113,16 +16394,16 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a } else { // Else we increment the drop count and check the amount. this.dropCount++; -@@ -1859,7 +1886,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -1857,7 +1884,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic case ABORT_DESTROY_BLOCK: case STOP_DESTROY_BLOCK: // Paper start - Don't allow digging in unloaded chunks -- if (this.player.level.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) == null) { -+ if (!io.papermc.paper.util.TickThread.isTickThreadFor(this.player.getLevel(), blockposition.getX() >> 4, blockposition.getZ() >> 4, 8) || this.player.level.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) == null) { // Folia - region threading - don't destroy blocks not owned +- if (this.player.level().getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) == null) { ++ if (!io.papermc.paper.util.TickThread.isTickThreadFor(this.player.serverLevel(), blockposition.getX() >> 4, blockposition.getZ() >> 4, 8) || this.player.level().getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) == null) { // Folia - region threading - don't destroy blocks not owned this.player.connection.ackBlockChangesUpTo(packet.getSequence()); return; } -@@ -1943,7 +1970,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -1941,7 +1968,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic BlockPos blockposition = movingobjectpositionblock.getBlockPos(); Vec3 vec3d1 = Vec3.atCenterOf(blockposition); @@ -17131,7 +16412,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a Vec3 vec3d2 = vec3d.subtract(vec3d1); double d0 = 1.0000001D; -@@ -2056,7 +2083,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2054,7 +2081,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic Entity entity = packet.getEntity(worldserver); if (entity != null) { @@ -17140,7 +16421,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a return; } } -@@ -2119,6 +2146,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2117,6 +2144,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic this.player.disconnect(); // Paper start - Adventure quitMessage = quitMessage == null ? this.server.getPlayerList().remove(this.player) : this.server.getPlayerList().remove(this.player, quitMessage); // Paper - pass in quitMessage to fix kick message not being used @@ -17149,7 +16430,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a if ((quitMessage != null) && !quitMessage.equals(net.kyori.adventure.text.Component.empty())) { this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(quitMessage), false); // Paper end -@@ -2204,9 +2233,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2202,9 +2231,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic } // CraftBukkit end if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.message())) { @@ -17161,7 +16442,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a } else { Optional optional = this.tryHandleChat(packet.message(), packet.timeStamp(), packet.lastSeenMessages()); -@@ -2240,23 +2269,22 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2238,23 +2267,22 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic @Override public void handleChatCommand(ServerboundChatCommandPacket packet) { if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.command())) { @@ -17189,7 +16470,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a } } -@@ -2330,9 +2358,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2328,9 +2356,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic private Optional tryHandleChat(String message, Instant timestamp, LastSeenMessages.Update acknowledgment) { if (!this.updateChatOrder(timestamp)) { ServerGamePacketListenerImpl.LOGGER.warn("{} sent out-of-order chat: '{}': {} > {}", this.player.getName().getString(), message, this.lastChatTimeStamp.get().getEpochSecond(), timestamp.getEpochSecond()); // Paper @@ -17201,7 +16482,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a return Optional.empty(); } else { Optional optional = this.unpackAndApplyLastSeen(acknowledgment); -@@ -2407,7 +2435,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2405,7 +2433,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic String originalFormat = event.getFormat(), originalMessage = event.getMessage(); this.cserver.getPluginManager().callEvent(event); @@ -17210,7 +16491,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a // Evil plugins still listening to deprecated event final PlayerChatEvent queueEvent = new PlayerChatEvent(player, event.getMessage(), event.getFormat(), event.getRecipients()); queueEvent.setCancelled(event.isCancelled()); -@@ -2485,6 +2513,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2483,6 +2511,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic public void handleCommand(String s) { // Paper - private -> public // Paper Start if (!org.spigotmc.AsyncCatcher.shuttingDown && !org.bukkit.Bukkit.isPrimaryThread()) { @@ -17218,7 +16499,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a LOGGER.error("Command Dispatched Async: " + s); LOGGER.error("Please notify author of plugin causing this execution to fix this bug! see: http://bit.ly/1oSiM6C", new Throwable()); Waitable wait = new Waitable<>() { -@@ -2545,6 +2574,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2543,6 +2572,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic if (s.isEmpty()) { ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send an empty message"); } else if (this.getCraftPlayer().isConversing()) { @@ -17226,7 +16507,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a final String conversationInput = s; this.server.processQueue.add(new Runnable() { @Override -@@ -2786,7 +2816,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2784,7 +2814,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic this.player.resetLastActionTime(); this.player.setShiftKeyDown(packet.isUsingSecondaryAction()); @@ -17235,7 +16516,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a if (!worldserver.getWorldBorder().isWithinBounds(entity.blockPosition())) { return; } -@@ -2926,6 +2956,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2924,6 +2954,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic switch (packetplayinclientcommand_enumclientcommand) { case PERFORM_RESPAWN: if (this.player.wonGame) { @@ -17248,7 +16529,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a this.player.wonGame = false; this.player = this.server.getPlayerList().respawn(this.player, this.server.getLevel(this.player.getRespawnDimension()), true, null, true, RespawnReason.END_PORTAL, org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag.END_PORTAL); // Paper - add isEndCreditsRespawn argument CriteriaTriggers.CHANGED_DIMENSION.trigger(this.player, Level.END, Level.OVERWORLD); -@@ -2934,6 +2970,18 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -2932,6 +2968,18 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic return; } @@ -17267,16 +16548,16 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a this.player = this.server.getPlayerList().respawn(this.player, false, RespawnReason.DEATH); if (this.server.isHardcore()) { this.player.setGameMode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper -@@ -3286,7 +3334,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -3285,7 +3333,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic // Paper start if (!org.bukkit.Bukkit.isPrimaryThread()) { - if (recipeSpamPackets.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamLimit) { -- server.scheduleOnMain(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause + if (this.recipeSpamPackets.addAndGet(io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamIncrement) > io.papermc.paper.configuration.GlobalConfiguration.get().spamLimiter.recipeSpamLimit) { +- this.server.scheduleOnMain(() -> this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM)); // Paper - kick event cause + this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam", new Object[0]), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause // Folia - region threading return; } } -@@ -3428,7 +3476,18 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -3427,7 +3475,18 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic this.filterTextPacket(list).thenAcceptAsync((list1) -> { this.updateSignText(packet, list1); @@ -17296,7 +16577,7 @@ index aa287d7f37f38d938d195114408cb6dbda59063d..e1e5573b31ca071eac727383b21a085a } private void updateSignText(ServerboundSignUpdatePacket packet, List signText) { -@@ -3498,9 +3557,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -3460,9 +3519,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic this.keepAlivePending = false; } else if (!this.isSingleplayerOwner()) { // Paper start - This needs to be handled on the main thread for plugins @@ -17517,7 +16798,7 @@ index 7edd4b88eb0476f0630630bc4681e859bd145b2b..f3586a5c5b5d4cae817aa7c15fc0c2fc date1 = fallback; } diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313909c9a7d 100644 +index 647607827949302098ff45ffa4296ed62511987a..576e60f2ce00733254895f83f4bef76152dc3fe9 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -139,10 +139,10 @@ public abstract class PlayerList { @@ -17600,7 +16881,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 player.isRealPlayer = true; // Paper player.loginTime = System.currentTimeMillis(); // Paper GameProfile gameprofile = player.getGameProfile(); -@@ -241,9 +288,30 @@ public abstract class PlayerList { +@@ -248,9 +295,30 @@ public abstract class PlayerList { // Paper start if (nbttagcompound == null) { player.spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT; // set Player SpawnReason to DEFAULT on first login @@ -17629,10 +16910,10 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 + player.setPosRaw(selectedSpawn.getX(), selectedSpawn.getY(), selectedSpawn.getZ()); + player.lastSave = System.nanoTime(); // changed to nanoTime + // Folia end - region threading - rewrite login process - player.setLevel(worldserver1); + player.setServerLevel(worldserver1); String s1 = "local"; -@@ -254,7 +322,7 @@ public abstract class PlayerList { +@@ -261,7 +329,7 @@ public abstract class PlayerList { // Spigot start - spawn location event Player spawnPlayer = player.getBukkitEntity(); org.spigotmc.event.player.PlayerSpawnLocationEvent ev = new com.destroystokyo.paper.event.player.PlayerInitialSpawnEvent(spawnPlayer, spawnPlayer.getLocation()); // Paper use our duplicate event @@ -17641,7 +16922,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 Location loc = ev.getSpawnLocation(); worldserver1 = ((CraftWorld) loc.getWorld()).getHandle(); -@@ -273,6 +341,7 @@ public abstract class PlayerList { +@@ -280,6 +348,7 @@ public abstract class PlayerList { player.loadGameTypes(nbttagcompound); ServerGamePacketListenerImpl playerconnection = new ServerGamePacketListenerImpl(this.server, connection, player); @@ -17649,7 +16930,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 GameRules gamerules = worldserver1.getGameRules(); boolean flag = gamerules.getBoolean(GameRules.RULE_DO_IMMEDIATE_RESPAWN); boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO); -@@ -290,7 +359,7 @@ public abstract class PlayerList { +@@ -297,7 +366,7 @@ public abstract class PlayerList { this.sendPlayerPermissionLevel(player); player.getStats().markAllDirty(); player.getRecipeBook().sendInitialRecipeBook(player); @@ -17658,7 +16939,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 this.server.invalidateStatus(); MutableComponent ichatmutablecomponent; -@@ -332,7 +401,7 @@ public abstract class PlayerList { +@@ -339,7 +408,7 @@ public abstract class PlayerList { this.cserver.getPluginManager().callEvent(playerJoinEvent); if (!player.connection.isAcceptingMessages()) { @@ -17667,7 +16948,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 } final net.kyori.adventure.text.Component jm = playerJoinEvent.joinMessage(); -@@ -347,8 +416,7 @@ public abstract class PlayerList { +@@ -354,8 +423,7 @@ public abstract class PlayerList { ClientboundPlayerInfoUpdatePacket packet = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player)); final List onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); // Paper - use single player info update packet @@ -17677,7 +16958,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 if (entityplayer1.getBukkitEntity().canSee(bukkitPlayer)) { entityplayer1.connection.send(packet); -@@ -465,7 +533,7 @@ public abstract class PlayerList { +@@ -472,7 +540,7 @@ public abstract class PlayerList { // Paper start - Add to collideRule team if needed final Scoreboard scoreboard = this.getServer().getLevel(Level.OVERWORLD).getScoreboard(); final PlayerTeam collideRuleTeam = scoreboard.getPlayerTeam(this.collideRuleTeamName); @@ -17686,7 +16967,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam); } // Paper end -@@ -556,7 +624,7 @@ public abstract class PlayerList { +@@ -563,7 +631,7 @@ public abstract class PlayerList { protected void save(ServerPlayer player) { if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit @@ -17695,7 +16976,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 this.playerIo.save(player); ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit -@@ -596,7 +664,7 @@ public abstract class PlayerList { +@@ -603,7 +671,7 @@ public abstract class PlayerList { // CraftBukkit end // Paper start - Remove from collideRule team if needed @@ -17704,7 +16985,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 final Scoreboard scoreBoard = this.server.getLevel(Level.OVERWORLD).getScoreboard(); final PlayerTeam team = scoreBoard.getPlayersTeam(this.collideRuleTeamName); if (entityplayer.getTeam() == team && team != null) { -@@ -636,6 +704,7 @@ public abstract class PlayerList { +@@ -643,6 +711,7 @@ public abstract class PlayerList { entityplayer.unRide(); worldserver.removePlayerImmediately(entityplayer, Entity.RemovalReason.UNLOADED_WITH_PLAYER); @@ -17712,7 +16993,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 entityplayer.getAdvancements().stopListening(); this.players.remove(entityplayer); this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot -@@ -654,8 +723,7 @@ public abstract class PlayerList { +@@ -661,8 +730,7 @@ public abstract class PlayerList { // CraftBukkit start // this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(entityplayer.getUUID()))); ClientboundPlayerInfoRemovePacket packet = new ClientboundPlayerInfoRemovePacket(List.of(entityplayer.getUUID())); @@ -17722,7 +17003,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 if (entityplayer2.getBukkitEntity().canSee(entityplayer.getBukkitEntity())) { entityplayer2.connection.send(packet); -@@ -680,19 +748,13 @@ public abstract class PlayerList { +@@ -687,19 +755,13 @@ public abstract class PlayerList { ServerPlayer entityplayer; @@ -17744,7 +17025,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 } // Instead of kicking then returning, we need to store the kick reason -@@ -711,7 +773,7 @@ public abstract class PlayerList { +@@ -718,7 +780,7 @@ public abstract class PlayerList { ichatmutablecomponent = Component.translatable("multiplayer.disconnect.banned.reason", gameprofilebanentry.getReason()); if (gameprofilebanentry.getExpires() != null) { @@ -17753,7 +17034,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 } // return chatmessage; -@@ -724,14 +786,14 @@ public abstract class PlayerList { +@@ -731,14 +793,14 @@ public abstract class PlayerList { ichatmutablecomponent = Component.translatable("multiplayer.disconnect.banned_ip.reason", ipbanentry.getReason()); if (ipbanentry.getExpires() != null) { @@ -17770,7 +17051,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 event.disallow(PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure } } -@@ -789,6 +851,11 @@ public abstract class PlayerList { +@@ -796,6 +858,11 @@ public abstract class PlayerList { public ServerPlayer respawn(ServerPlayer entityplayer, ServerLevel worldserver, boolean flag, Location location, boolean avoidSuffocation, RespawnReason reason, org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag...respawnFlags) { // Paper end @@ -17782,7 +17063,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 entityplayer.stopRiding(); // CraftBukkit this.players.remove(entityplayer); this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot -@@ -983,10 +1050,10 @@ public abstract class PlayerList { +@@ -990,10 +1057,10 @@ public abstract class PlayerList { public void tick() { if (++this.sendAllPlayerInfoIn > 600) { // CraftBukkit start @@ -17796,7 +17077,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 @Override public boolean test(ServerPlayer input) { return target.getBukkitEntity().canSee(input.getBukkitEntity()); -@@ -1012,18 +1079,17 @@ public abstract class PlayerList { +@@ -1019,18 +1086,17 @@ public abstract class PlayerList { // CraftBukkit start - add a world/entity limited version public void broadcastAll(Packet packet, net.minecraft.world.entity.player.Player entityhuman) { @@ -17819,7 +17100,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 } } -@@ -1067,8 +1133,7 @@ public abstract class PlayerList { +@@ -1074,8 +1140,7 @@ public abstract class PlayerList { if (scoreboardteambase == null) { this.broadcastSystemMessage(message, false); } else { @@ -17829,7 +17110,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 if (entityplayer.getTeam() != scoreboardteambase) { entityplayer.sendSystemMessage(message); -@@ -1079,10 +1144,12 @@ public abstract class PlayerList { +@@ -1086,10 +1151,12 @@ public abstract class PlayerList { } public String[] getPlayerNamesArray() { @@ -17845,7 +17126,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 } return astring; -@@ -1101,7 +1168,9 @@ public abstract class PlayerList { +@@ -1108,7 +1175,9 @@ public abstract class PlayerList { ServerPlayer entityplayer = this.getPlayer(profile.getId()); if (entityplayer != null) { @@ -17855,7 +17136,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 } } -@@ -1111,7 +1180,10 @@ public abstract class PlayerList { +@@ -1118,7 +1187,10 @@ public abstract class PlayerList { ServerPlayer entityplayer = this.getPlayer(profile.getId()); if (entityplayer != null) { @@ -17866,7 +17147,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 } } -@@ -1172,8 +1244,7 @@ public abstract class PlayerList { +@@ -1179,8 +1251,7 @@ public abstract class PlayerList { } public void broadcast(@Nullable net.minecraft.world.entity.player.Player player, double x, double y, double z, double distance, ResourceKey worldKey, Packet packet) { @@ -17876,7 +17157,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 // CraftBukkit start - Test if player receiving packet can see the source of the packet if (player != null && !entityplayer.getBukkitEntity().canSee(player.getBukkitEntity())) { -@@ -1203,12 +1274,21 @@ public abstract class PlayerList { +@@ -1210,12 +1281,21 @@ public abstract class PlayerList { io.papermc.paper.util.MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main MinecraftTimings.savePlayers.startTiming(); // Paper int numSaved = 0; @@ -17903,7 +17184,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 } // Paper end } -@@ -1325,6 +1405,20 @@ public abstract class PlayerList { +@@ -1332,6 +1412,20 @@ public abstract class PlayerList { } public void removeAll(boolean isRestarting) { @@ -17924,7 +17205,7 @@ index 7679fa8c905e6cee1906814a82f8ab4ab925b0fc..7d99233f4b36d699aae81be554e78313 // Paper end // CraftBukkit start - disconnect safely for (ServerPlayer player : this.players) { -@@ -1334,7 +1428,7 @@ public abstract class PlayerList { +@@ -1341,7 +1435,7 @@ public abstract class PlayerList { // CraftBukkit end // Paper start - Remove collideRule team if it exists @@ -17966,10 +17247,10 @@ index 4fd709a550bf8da1e996894a1ca6b91206c31e9e..e07eddbfbe3fa5e5915580a0f4d753ce } } diff --git a/src/main/java/net/minecraft/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java -index 4f5f2c25e12ee6d977bc98d9118650cfe91e6c0e..d227b91defc3992f1a003a19264bc3aa29718795 100644 +index a6ac76707da39cf86113003b1f326433fdc86c86..531ccc48154b25378e9039478ce7f89be4740bce 100644 --- a/src/main/java/net/minecraft/util/SortedArraySet.java +++ b/src/main/java/net/minecraft/util/SortedArraySet.java -@@ -82,7 +82,7 @@ public class SortedArraySet extends AbstractSet { +@@ -90,7 +90,7 @@ public class SortedArraySet extends AbstractSet { return Arrays.binarySearch(this.contents, 0, this.size, object, this.comparator); } @@ -17978,7 +17259,7 @@ index 4f5f2c25e12ee6d977bc98d9118650cfe91e6c0e..d227b91defc3992f1a003a19264bc3aa return -binarySearchResult - 1; } -@@ -169,6 +169,40 @@ public class SortedArraySet extends AbstractSet { +@@ -177,6 +177,40 @@ public class SortedArraySet extends AbstractSet { } } // Paper end - rewrite chunk system @@ -18020,10 +17301,10 @@ index 4f5f2c25e12ee6d977bc98d9118650cfe91e6c0e..d227b91defc3992f1a003a19264bc3aa @Override public boolean remove(Object object) { diff --git a/src/main/java/net/minecraft/util/SpawnUtil.java b/src/main/java/net/minecraft/util/SpawnUtil.java -index 83ef8cb27db685cceb5c2b7c9674e17b93ba081c..2d87c16420a97b9142d4ea76ceb6013deed22a1f 100644 +index 028d69907a988e191213a17e072ef22710b5bc83..ffa081156313247882747ea6da182ee54a1cf8a0 100644 --- a/src/main/java/net/minecraft/util/SpawnUtil.java +++ b/src/main/java/net/minecraft/util/SpawnUtil.java -@@ -59,7 +59,7 @@ public class SpawnUtil { +@@ -63,7 +63,7 @@ public class SpawnUtil { return Optional.of(t0); } @@ -18033,39 +17314,39 @@ index 83ef8cb27db685cceb5c2b7c9674e17b93ba081c..2d87c16420a97b9142d4ea76ceb6013d } } diff --git a/src/main/java/net/minecraft/world/damagesource/CombatTracker.java b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java -index a454aa44acddd668c96a17da44ccccb1bbac4546..0e6112a7dbeb17b088e3585b0d284ec36e48ca80 100644 +index c12d7bacf2c54f268b1bc5e46250a083ca041415..08e84a81facb5514b4e26805dda1291db8936f1a 100644 --- a/src/main/java/net/minecraft/world/damagesource/CombatTracker.java +++ b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java -@@ -40,7 +40,7 @@ public class CombatTracker { - public void prepareForDamage() { - this.resetPreparedStatus(); - Optional optional = this.mob.getLastClimbablePos(); -- if (optional.isPresent()) { -+ if (optional.isPresent() && io.papermc.paper.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)this.mob.level, optional.get())) { // Folia - region threading - may sync load the block, which would crash - BlockState blockState = this.mob.level.getBlockState(optional.get()); - if (!blockState.is(Blocks.LADDER) && !blockState.is(BlockTags.TRAPDOORS)) { - if (blockState.is(Blocks.VINE)) { -@@ -92,6 +92,7 @@ public class CombatTracker { - Component component = combatEntry2.getAttackerName(); - DamageSource damageSource = combatEntry2.getSource(); - Entity entity = damageSource.getEntity(); -+ if (!io.papermc.paper.util.TickThread.isTickThreadFor(entity)) entity = null; // Folia - region threading - not safe to access other entity data - DeathMessageType deathMessageType = damageSource.type().deathMessageType(); - Component component4; - if (combatEntry != null && deathMessageType == DeathMessageType.FALL_VARIANTS) { -@@ -158,6 +159,7 @@ public class CombatTracker { +@@ -52,7 +52,7 @@ public class CombatTracker { - for(CombatEntry combatEntry : this.entries) { - Entity var8 = combatEntry.getSource().getEntity(); -+ if (!io.papermc.paper.util.TickThread.isTickThreadFor(var8)) { continue; } // Folia - region threading - skip entities we do not own - if (var8 instanceof Player player2) { - if (player == null || combatEntry.getDamage() > g) { - g = combatEntry.getDamage(); + private Component getMessageForAssistedFall(Entity attacker, Component attackerDisplayName, String itemDeathTranslationKey, String deathTranslationKey) { + ItemStack var10000; +- if (attacker instanceof LivingEntity livingEntity) { ++ if (attacker instanceof LivingEntity livingEntity && io.papermc.paper.util.TickThread.isTickThreadFor(livingEntity)) { // Folia - region threading + var10000 = livingEntity.getMainHandItem(); + } else { + var10000 = ItemStack.EMPTY; +@@ -81,7 +81,7 @@ public class CombatTracker { + + @Nullable + private static Component getDisplayName(@Nullable Entity entity) { +- return entity == null ? null : entity.getDisplayName(); ++ return entity == null || !io.papermc.paper.util.TickThread.isTickThreadFor(entity) ? null : entity.getDisplayName(); // Folia - region threading + } + + public Component getDeathMessage() { diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -index 93a1e990b0a6caae4143c2f9d09bfb368fa1d6db..ad3166481dd37f4b5380f8bf28653bb4fd5c4933 100644 +index 25a5a3b949a0eb632611355e74ccd4865be108ca..1df8d601e41c2ab35921b6a1534fdec6e6353954 100644 --- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java +++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -@@ -112,7 +112,7 @@ public class DamageSource { +@@ -106,13 +106,13 @@ public class DamageSource { + LivingEntity entityliving1 = killed.getKillCredit(); + String s1 = s + ".player"; + +- return entityliving1 != null ? Component.translatable(s1, killed.getDisplayName(), entityliving1.getDisplayName()) : Component.translatable(s, killed.getDisplayName()); ++ return entityliving1 != null && io.papermc.paper.util.TickThread.isTickThreadFor(entityliving1) ? Component.translatable(s1, killed.getDisplayName(), entityliving1.getDisplayName()) : Component.translatable(s, killed.getDisplayName()); // Folia - region threading + } else { + Component ichatbasecomponent = this.causingEntity == null ? this.directEntity.getDisplayName() : this.causingEntity.getDisplayName(); Entity entity = this.causingEntity; ItemStack itemstack; @@ -18075,7 +17356,7 @@ index 93a1e990b0a6caae4143c2f9d09bfb368fa1d6db..ad3166481dd37f4b5380f8bf28653bb4 itemstack = entityliving2.getMainHandItem(); diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb6791abddf19 100644 +index 92202262eff01ae3bbeff0e6ebdcf26ad613c169..154ae1492969c4fe9f56ddc190e27337d0396da8 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -166,7 +166,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { @@ -18119,7 +17400,7 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 @Override public CommandSender getBukkitSender(CommandSourceStack wrapper) { return this.getBukkitEntity(); -@@ -491,28 +503,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -495,28 +507,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { this.isLegacyTrackingEntity = isLegacyTrackingEntity; } @@ -18149,7 +17430,7 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 // Paper end - optimise entity tracking // Paper start - make end portalling safe public BlockPos portalBlock; -@@ -544,6 +535,25 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -548,6 +539,25 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { this.teleportTo(worldserver, null); } // Paper end - make end portalling safe @@ -18175,7 +17456,7 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 public Entity(EntityType type, Level world) { this.id = Entity.ENTITY_COUNTER.incrementAndGet(); -@@ -659,6 +669,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -665,6 +675,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } public final void discard() { @@ -18187,7 +17468,7 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 this.remove(Entity.RemovalReason.DISCARDED); } -@@ -783,6 +798,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -789,6 +804,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { // CraftBukkit start public void postTick() { @@ -18200,7 +17481,7 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities this.handleNetherPortal(); -@@ -805,7 +826,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -811,7 +832,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { this.walkDistO = this.walkDist; this.xRotO = this.getXRot(); this.yRotO = this.getYRot(); @@ -18209,7 +17490,7 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 if (this.canSpawnSprintParticle()) { this.spawnSprintParticle(); } -@@ -906,11 +927,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -920,11 +941,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { // This will be called every single tick the entity is in lava, so don't throw an event this.setSecondsOnFire(15, false); } @@ -18223,7 +17504,7 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 // CraftBukkit end - we also don't throw an event unless the object in lava is living, to save on some event calls } -@@ -1018,8 +1039,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -1069,8 +1090,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } else { this.wasOnFire = this.isOnFire(); if (movementType == MoverType.PISTON) { @@ -18234,7 +17515,7 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 movement = this.limitPistonMovement(movement); if (movement.equals(Vec3.ZERO)) { return; -@@ -3092,6 +3113,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -3216,6 +3237,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { @Nullable public Team getTeam() { @@ -18243,10 +17524,10 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 + return null; + } + // Folia end - region threading - if (!this.level.paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof Player)) { return null; } // Paper - return this.level.getScoreboard().getPlayersTeam(this.getScoreboardName()); + if (!this.level().paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof Player)) { return null; } // Paper + return this.level().getScoreboard().getPlayersTeam(this.getScoreboardName()); } -@@ -3207,9 +3233,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -3331,9 +3357,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { if (this.fireImmune()) { return; } @@ -18258,7 +17539,7 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 return; } // CraftBukkit end -@@ -3382,6 +3408,763 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -3506,6 +3532,763 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { this.portalEntrancePos = original.portalEntrancePos; } @@ -19022,7 +18303,7 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 @Nullable public Entity changeDimension(ServerLevel destination) { // CraftBukkit start -@@ -3390,6 +4173,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -3514,6 +4297,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { @Nullable public Entity teleportTo(ServerLevel worldserver, PositionImpl location) { @@ -19033,8 +18314,8 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 + // Folia end - region threading // CraftBukkit end // Paper start - fix bad state entities causing dupes - if (!isAlive() || !valid) { -@@ -3473,6 +4261,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + if (!this.isAlive() || !this.valid) { +@@ -3597,6 +4385,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } } @@ -19047,7 +18328,7 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 protected void removeAfterChangingDimensions() { this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION); } -@@ -3912,17 +4706,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -4041,17 +4835,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { // Paper start public void startSeenByPlayer(ServerPlayer player) { @@ -19067,7 +18348,7 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 } // Paper end -@@ -4413,7 +5203,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -4546,7 +5336,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } } // Paper end - fix MC-4 @@ -19077,7 +18358,7 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 synchronized (this.posLock) { // Paper this.position = new Vec3(x, y, z); } // Paper -@@ -4434,7 +5225,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -4567,7 +5358,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 @@ -19086,7 +18367,7 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 this.setBoundingBox(this.makeBoundingBox()); } // Paper end -@@ -4521,6 +5312,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -4654,6 +5445,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { return this.removalReason != null; } @@ -19099,7 +18380,7 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 @Nullable public Entity.RemovalReason getRemovalReason() { return this.removalReason; -@@ -4545,7 +5342,23 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -4678,7 +5475,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); @@ -19124,36 +18405,35 @@ index 280ee1838106201f5e3ba7753caced6d030f7e55..e8e6743ec078d9dd95d3583ff24cb679 public void unsetRemoved() { this.removalReason = null; diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 95a27d28f73039693ca64601954af62028413634..4f8062432cfe6480664e674fde0255f07b15c73a 100644 +index 67627bbf84f5aab2872f636b1dcb6728c8494147..7da79a21f5ff0bd546cfc602099916dac2ab0e51 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -464,7 +464,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -482,7 +482,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (this.isDeadOrDying() && this.level.shouldTickDeath(this)) { + if (this.isDeadOrDying() && this.level().shouldTickDeath(this)) { this.tickDeath(); - } + } else { this.broadcastedDeath = false; } // Folia - region threading if (this.lastHurtByPlayerTime > 0) { --this.lastHurtByPlayerTime; -@@ -610,11 +610,14 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -628,11 +628,13 @@ public abstract class LivingEntity extends Entity implements Attackable { return true; } + public boolean broadcastedDeath = false; // Folia - region threading protected void tickDeath() { ++this.deathTime; -- if (this.deathTime >= 20 && !this.level.isClientSide() && !this.isRemoved()) { -+ if (this.deathTime >= 20 && !this.level.isClientSide() && !this.isRemoved() && !this.broadcastedDeath) { // Folia - region threading -+ this.broadcastedDeath = true; // Folia - region threading - this.level.broadcastEntityEvent(this, (byte) 60); +- if (this.deathTime >= 20 && !this.level().isClientSide() && !this.isRemoved()) { ++ if (this.deathTime >= 20 && !this.level().isClientSide() && !this.isRemoved() && !this.broadcastedDeath) { // Folia - region threading + this.level().broadcastEntityEvent(this, (byte) 60); - this.remove(Entity.RemovalReason.KILLED); + if (!(this instanceof ServerPlayer)) this.remove(Entity.RemovalReason.KILLED); // Folia - region threading - don't remove, we want the tick scheduler to be running + if ((this instanceof ServerPlayer)) this.unRide(); // Folia - region threading - unmount player when dead } } -@@ -835,9 +838,9 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -853,9 +855,9 @@ public abstract class LivingEntity extends Entity implements Attackable { } this.hurtTime = nbt.getShort("HurtTime"); @@ -19163,9 +18443,9 @@ index 95a27d28f73039693ca64601954af62028413634..4f8062432cfe6480664e674fde0255f0 - if (nbt.contains("Team", 8)) { + if (false && nbt.contains("Team", 8)) { // Folia start - region threading String s = nbt.getString("Team"); - PlayerTeam scoreboardteam = this.level.getScoreboard().getPlayerTeam(s); - if (!level.paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof net.minecraft.world.entity.player.Player)) { scoreboardteam = null; } // Paper -@@ -1117,7 +1120,7 @@ public abstract class LivingEntity extends Entity implements Attackable { + PlayerTeam scoreboardteam = this.level().getScoreboard().getPlayerTeam(s); + if (!this.level().paperConfig().scoreboards.allowNonPlayerEntitiesOnScoreboards && !(this instanceof net.minecraft.world.entity.player.Player)) { scoreboardteam = null; } // Paper +@@ -1135,7 +1137,7 @@ public abstract class LivingEntity extends Entity implements Attackable { } public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause) { @@ -19174,27 +18454,27 @@ index 95a27d28f73039693ca64601954af62028413634..4f8062432cfe6480664e674fde0255f0 if (this.isTickingEffects) { this.effectsToProcess.add(new ProcessableEffect(mobeffect, cause)); return true; -@@ -2266,7 +2269,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -2307,7 +2309,7 @@ public abstract class LivingEntity extends Entity implements Attackable { @Nullable public LivingEntity getKillCredit() { -- return (LivingEntity) (this.combatTracker.getKiller() != null ? this.combatTracker.getKiller() : (this.lastHurtByPlayer != null ? this.lastHurtByPlayer : (this.lastHurtByMob != null ? this.lastHurtByMob : null))); -+ return (LivingEntity) (this.combatTracker.getKiller() != null ? this.combatTracker.getKiller() : (this.lastHurtByPlayer != null && io.papermc.paper.util.TickThread.isTickThreadFor(this.lastHurtByPlayer) ? this.lastHurtByPlayer : (this.lastHurtByMob != null && io.papermc.paper.util.TickThread.isTickThreadFor(this.lastHurtByMob) ? this.lastHurtByMob : null))); // Folia - region threading +- return (LivingEntity) (this.lastHurtByPlayer != null ? this.lastHurtByPlayer : (this.lastHurtByMob != null ? this.lastHurtByMob : null)); ++ return (LivingEntity) (this.lastHurtByPlayer != null && io.papermc.paper.util.TickThread.isTickThreadFor(this.lastHurtByPlayer) ? this.lastHurtByPlayer : (this.lastHurtByMob != null && io.papermc.paper.util.TickThread.isTickThreadFor(this.lastHurtByMob) ? this.lastHurtByMob : null)); // Folia - region threading } public final float getMaxHealth() { -@@ -3394,7 +3397,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -3443,7 +3445,7 @@ public abstract class LivingEntity extends Entity implements Attackable { this.pushEntities(); - this.level.getProfiler().pop(); + this.level().getProfiler().pop(); // Paper start -- if (((ServerLevel) this.level).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { -+ if (((ServerLevel) this.level).getCurrentWorldData().hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { - if (this.xo != getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { - Location from = new Location(this.level.getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO); - Location to = new Location (this.level.getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); -@@ -4054,7 +4057,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +- if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { ++ if (((ServerLevel) this.level()).getCurrentWorldData().hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { // Folia - region threading + if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { + Location from = new Location(this.level().getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO); + Location to = new Location (this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); +@@ -4103,7 +4105,7 @@ public abstract class LivingEntity extends Entity implements Attackable { BlockPos blockposition = BlockPos.containing(d0, d1, d2); - Level world = this.level; + Level world = this.level(); - if (world.hasChunkAt(blockposition)) { + if (io.papermc.paper.util.TickThread.isTickThreadFor((ServerLevel)world, blockposition) && world.hasChunkAt(blockposition)) { // Folia - region threading @@ -19202,10 +18482,10 @@ index 95a27d28f73039693ca64601954af62028413634..4f8062432cfe6480664e674fde0255f0 while (!flag2 && blockposition.getY() > world.getMinBuildHeight()) { diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 02cb6b8c1d59855ff4a8aad3024fe12007eca0ee..8ea463720fde25954c208d1e91571f1494d24aea 100644 +index e2a25c29ec74147b3e66aa0b3deb85a8f6ee53a5..a8b23b1594d2b39568c68c93a8a1b936457672bc 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -135,6 +135,14 @@ public abstract class Mob extends LivingEntity implements Targeting { +@@ -134,6 +134,14 @@ public abstract class Mob extends LivingEntity implements Targeting { public boolean aware = true; // CraftBukkit @@ -19220,7 +18500,7 @@ index 02cb6b8c1d59855ff4a8aad3024fe12007eca0ee..8ea463720fde25954c208d1e91571f14 protected Mob(EntityType type, Level world) { super(type, world); this.handItems = NonNullList.withSize(2, ItemStack.EMPTY); -@@ -283,9 +291,21 @@ public abstract class Mob extends LivingEntity implements Targeting { +@@ -286,9 +294,21 @@ public abstract class Mob extends LivingEntity implements Targeting { @Nullable @Override public LivingEntity getTarget() { @@ -19242,7 +18522,7 @@ index 02cb6b8c1d59855ff4a8aad3024fe12007eca0ee..8ea463720fde25954c208d1e91571f14 public org.bukkit.craftbukkit.entity.CraftMob getBukkitMob() { return (org.bukkit.craftbukkit.entity.CraftMob) super.getBukkitEntity(); } // Paper public void setTarget(@Nullable LivingEntity target) { // CraftBukkit start - fire event -@@ -293,7 +313,7 @@ public abstract class Mob extends LivingEntity implements Targeting { +@@ -296,7 +316,7 @@ public abstract class Mob extends LivingEntity implements Targeting { } public boolean setTarget(LivingEntity entityliving, EntityTargetEvent.TargetReason reason, boolean fireEvent) { @@ -19251,30 +18531,30 @@ index 02cb6b8c1d59855ff4a8aad3024fe12007eca0ee..8ea463720fde25954c208d1e91571f14 if (fireEvent) { if (reason == EntityTargetEvent.TargetReason.UNKNOWN && this.getTarget() != null && entityliving == null) { reason = this.getTarget().isAlive() ? EntityTargetEvent.TargetReason.FORGOT_TARGET : EntityTargetEvent.TargetReason.TARGET_DIED; -@@ -855,12 +875,7 @@ public abstract class Mob extends LivingEntity implements Targeting { - if (this.level.getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { +@@ -858,12 +878,7 @@ public abstract class Mob extends LivingEntity implements Targeting { + if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { this.discard(); } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) { - // Paper start - optimise checkDespawn -- Player entityhuman = this.level.findNearbyPlayer(this, level.paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory()).hard() + 1, EntitySelector.PLAYER_AFFECTS_SPAWNING); // Paper +- Player entityhuman = this.level().findNearbyPlayer(this, level().paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory()).hard() + 1, EntitySelector.PLAYER_AFFECTS_SPAWNING); // Paper - if (entityhuman == null) { -- entityhuman = ((ServerLevel)this.level).playersAffectingSpawning.isEmpty() ? null : ((ServerLevel)this.level).playersAffectingSpawning.get(0); +- entityhuman = ((ServerLevel)this.level()).playersAffectingSpawning.isEmpty() ? null : ((ServerLevel)this.level()).playersAffectingSpawning.get(0); - } - // Paper end - optimise checkDespawn -+ Player entityhuman = this.level.getNearestPlayer(this, -1.0D); // Folia - region threading ++ Player entityhuman = this.level().getNearestPlayer(this, -1.0D); // Folia - region threading if (entityhuman != null) { double d0 = entityhuman.distanceToSqr((Entity) this); -@@ -903,7 +918,7 @@ public abstract class Mob extends LivingEntity implements Targeting { - this.level.getProfiler().push("sensing"); +@@ -906,7 +921,7 @@ public abstract class Mob extends LivingEntity implements Targeting { + this.level().getProfiler().push("sensing"); this.sensing.tick(); - this.level.getProfiler().pop(); -- int i = this.level.getServer().getTickCount() + this.getId(); + this.level().getProfiler().pop(); +- int i = this.level().getServer().getTickCount() + this.getId(); + int i = this.tickCount + this.getId(); // Folia - region threading if (i % 2 != 0 && this.tickCount > 1) { - this.level.getProfiler().push("targetSelector"); -@@ -1728,6 +1743,15 @@ public abstract class Mob extends LivingEntity implements Targeting { + this.level().getProfiler().push("targetSelector"); +@@ -1730,6 +1745,15 @@ public abstract class Mob extends LivingEntity implements Targeting { this.goalSelector.removeAllGoals(predicate); } @@ -19290,19 +18570,22 @@ index 02cb6b8c1d59855ff4a8aad3024fe12007eca0ee..8ea463720fde25954c208d1e91571f14 @Override protected void removeAfterChangingDimensions() { super.removeAfterChangingDimensions(); -@@ -1736,9 +1760,7 @@ public abstract class Mob extends LivingEntity implements Targeting { - this.level.getCraftServer().getPluginManager().callEvent(event); // CraftBukkit +@@ -1738,12 +1762,7 @@ public abstract class Mob extends LivingEntity implements Targeting { + this.level().getCraftServer().getPluginManager().callEvent(event); // CraftBukkit this.dropLeash(true, event.isDropLeash()); // Paper end - this.getAllSlots().forEach((itemstack) -> { -- if (!itemstack.isEmpty()) itemstack.setCount(0); // CraftBukkit +- if (!itemstack.isEmpty()) { +- itemstack.setCount(0); +- } +- - }); + // Folia - region threading - move inventory clearing until after the dimension change - move into postRemoveAfterChangingDimensions } @Nullable diff --git a/src/main/java/net/minecraft/world/entity/ai/Brain.java b/src/main/java/net/minecraft/world/entity/ai/Brain.java -index 328c3ecd0d35d2cad15173ec80962cee9177eaf8..625e03abc4a62f42774fedee31a5f5f776169674 100644 +index a2de99709ce14303a309806c683da6b3548659c1..082b0594d5de2e952e0b2b64e76c1db3ddba82b9 100644 --- a/src/main/java/net/minecraft/world/entity/ai/Brain.java +++ b/src/main/java/net/minecraft/world/entity/ai/Brain.java @@ -412,9 +412,17 @@ public class Brain { @@ -19317,7 +18600,7 @@ index 328c3ecd0d35d2cad15173ec80962cee9177eaf8..625e03abc4a62f42774fedee31a5f5f7 + return; + } + // Folia end - region threading - long l = entity.level.getGameTime(); + long l = entity.level().getGameTime(); - for(BehaviorControl behaviorControl : this.getRunningBehaviors()) { + for(BehaviorControl behaviorControl : behaviors) { // Folia - region threading @@ -19342,7 +18625,7 @@ index 8ec07578c1e41997a2e5ef158885ad3f4c2a31b6..6dcacfca6eb4a8a6425f1aaeb57733d2 context.>get(mobs).stream().filter((mob) -> { return mob instanceof Villager && mob != entity; diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java -index 4fe177c950c769f5c6e76b522019b5d5b78259a5..624a4cc8a03880b74fb44bf16e0e35e4398eb1f9 100644 +index 13f96d7c1f2d920172f49fcd82d719f0416ffcee..c10db8960da0352220e57fdcb68f9cde6bcd23f8 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java @@ -70,7 +70,7 @@ public class FollowOwnerGoal extends Goal { @@ -19399,7 +18682,7 @@ index 4fe177c950c769f5c6e76b522019b5d5b78259a5..624a4cc8a03880b74fb44bf16e0e35e4 this.navigation.stop(); return true; diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java -index 7ffe5bef3778d5971ea4ceadf3102725fd0d08cd..bd6910c279a3a319744d3c7c889a889b892a8f76 100644 +index b376670d11088e524ce246f667e580e90cd119a3..2549b81eb5fa1a021edac960170f5e0d513dae97 100644 --- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java @@ -80,11 +80,11 @@ public abstract class PathNavigation { @@ -19578,7 +18861,7 @@ index fed09b886f4fa0006d160e5f2abb00dfee45434d..69075026c25ed1ce0f3c769ea0e4a8f3 SIEGE_CAN_ACTIVATE, SIEGE_TONIGHT, SIEGE_DONE; diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -index 9be85eb0abec02bc0e0eded71c34ab1c565c63e7..9c56304476b4fc841b5d7694232617586ebd8e84 100644 +index 12a7aaeaa8b4b788b620b1985591c3b93253ccd5..5150d447c9dc2f539446749c8bee102050bab4ed 100644 --- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java +++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java @@ -48,11 +48,13 @@ public class PoiManager extends SectionStorage { @@ -19617,7 +18900,7 @@ index 9be85eb0abec02bc0e0eded71c34ab1c565c63e7..9c56304476b4fc841b5d769423261758 @Override diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java -index 72b30a5cdeb8a43702d9ab5f198311929761fad1..3951a1f740839f286a190f3fda66132fc4e7ead5 100644 +index 90ce201bc7c47cef9bc59d7b535a7453854bac75..9c7c116a7d3570ccf5b30d55d68c420fec0d8ab5 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Cat.java +++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java @@ -363,7 +363,7 @@ public class Cat extends TamableAnimal implements VariantHolder { @@ -19630,7 +18913,7 @@ index 72b30a5cdeb8a43702d9ab5f198311929761fad1..3951a1f740839f286a190f3fda66132f this.setPersistenceRequired(); } diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -index 81dab77f525ae667614f940c4ff5ec308a9579a2..5fdb9ab7c6c0ae6bb948a44c5578c4304e8d6c3f 100644 +index 9f7fa132997829e9a34aaae7aac7a6f7d529eee2..3128c74e219a9be939eac9a2f723cd4b8b7d8a61 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java +++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java @@ -337,9 +337,9 @@ public class Turtle extends Animal { @@ -19646,11 +18929,11 @@ index 81dab77f525ae667614f940c4ff5ec308a9579a2..5fdb9ab7c6c0ae6bb948a44c5578c430 private static class TurtleMoveControl extends MoveControl { diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -index 30aec9dff249ae629b22318e52902361a9fa4099..b452b566693558fa714eca2db08ca775e73d48a0 100644 +index 955316687e2e29ad75a0052317a7b0f89034c82a..aedd3174c710049e4689f1a13a4f31522a8f58f3 100644 --- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java @@ -294,8 +294,10 @@ public class ItemFrame extends HangingEntity { - MapItemSavedData worldmap = MapItem.getSavedData(i, this.level); + MapItemSavedData worldmap = MapItem.getSavedData(i, this.level()); if (worldmap != null) { + synchronized (worldmap) { // Folia - make map data thread-safe @@ -19661,23 +18944,23 @@ index 30aec9dff249ae629b22318e52902361a9fa4099..b452b566693558fa714eca2db08ca775 }); diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java -index 536856300da929c101f50da5827677bada5feb50..b18c4f74f82a4953643f7b6bd684246ad28bccf1 100644 +index eff81e846f696349b3bd3d26c02442f157b169f0..6e35afb9e5314de69e78d819913418ab144bec52 100644 --- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java +++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java @@ -293,9 +293,9 @@ public class FallingBlockEntity extends Entity { float f2 = (float) Math.min(Mth.floor((float) i * this.fallDamagePerDistance), this.fallDamageMax); - this.level.getEntities((Entity) this, this.getBoundingBox(), predicate).forEach((entity) -> { + this.level().getEntities((Entity) this, this.getBoundingBox(), predicate).forEach((entity) -> { - CraftEventFactory.entityDamage = this; // CraftBukkit + CraftEventFactory.entityDamageRT.set(this); // CraftBukkit // Folia - region threading - entity.hurt(damagesource1, f2); + entity.hurt(damagesource2, f2); - CraftEventFactory.entityDamage = null; // CraftBukkit + CraftEventFactory.entityDamageRT.set(null); // CraftBukkit // Folia - region threading }); boolean flag = this.blockState.is(BlockTags.ANVIL); diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index d47b3ac633e7936d30abfda6fc46c2c7412d76fe..4245973677f1e2a3a75dce74c3e010330d0a9a41 100644 +index 3d41dbe0285f8fec8adae1e93010cf464df9b08c..6b1da5467b486b8c38fe95fd2313cac868fda343 100644 --- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java @@ -51,7 +51,7 @@ public class ItemEntity extends Entity implements TraceableEntity { @@ -19721,7 +19004,7 @@ index d47b3ac633e7936d30abfda6fc46c2c7412d76fe..4245973677f1e2a3a75dce74c3e01033 + // Folia - region threading - restore original timers this.hasImpulse |= this.updateInWaterStateAndDoFluidPushing(); - if (!this.level.isClientSide) { + if (!this.level().isClientSide) { @@ -216,13 +214,14 @@ public class ItemEntity extends Entity implements TraceableEntity { // Spigot start - copied from above @Override @@ -19742,9 +19025,9 @@ index d47b3ac633e7936d30abfda6fc46c2c7412d76fe..4245973677f1e2a3a75dce74c3e01033 + } + // Folia end - region threading - restore original timers - if (!this.level.isClientSide && this.age >= this.despawnRate) { // Spigot // Paper + if (!this.level().isClientSide && this.age >= this.despawnRate) { // Spigot // Paper // CraftBukkit start - fire ItemDespawnEvent -@@ -520,14 +519,20 @@ public class ItemEntity extends Entity implements TraceableEntity { +@@ -524,14 +523,20 @@ public class ItemEntity extends Entity implements TraceableEntity { return false; } @@ -19761,7 +19044,7 @@ index d47b3ac633e7936d30abfda6fc46c2c7412d76fe..4245973677f1e2a3a75dce74c3e01033 public Entity changeDimension(ServerLevel destination) { Entity entity = super.changeDimension(destination); -- if (!this.level.isClientSide && entity instanceof ItemEntity) { +- if (!this.level().isClientSide && entity instanceof ItemEntity) { - ((ItemEntity) entity).mergeWithNeighbours(); - } + if (entity != null) entity.postChangeDimension(); // Folia - region threading - move to post change @@ -19769,15 +19052,15 @@ index d47b3ac633e7936d30abfda6fc46c2c7412d76fe..4245973677f1e2a3a75dce74c3e01033 return entity; } diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -index dfdf7e7fc1070975ec18fd215c724f4fc84d3705..52150e27294fcd51ae23ea3ff06673fb1ca3d113 100644 +index bf3301eb1341ba9d482e10873447c42bd670f5ed..666d603b098edee635559f78fcfa89bb5c697c39 100644 --- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java @@ -60,7 +60,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { @Override public void tick() { -- if (level.spigotConfig.maxTntTicksPerTick > 0 && ++level.spigotConfig.currentPrimedTnt > level.spigotConfig.maxTntTicksPerTick) { return; } // Spigot -+ if (level.spigotConfig.maxTntTicksPerTick > 0 && ++level.getCurrentWorldData().currentPrimedTnt > level.spigotConfig.maxTntTicksPerTick) { return; } // Spigot // Folia - region threading +- if (this.level().spigotConfig.maxTntTicksPerTick > 0 && ++this.level().spigotConfig.currentPrimedTnt > this.level().spigotConfig.maxTntTicksPerTick) { return; } // Spigot ++ if (this.level().spigotConfig.maxTntTicksPerTick > 0 && ++this.level().getCurrentWorldData().currentPrimedTnt > this.level().spigotConfig.maxTntTicksPerTick) { return; } // Spigot // Folia - region threading if (!this.isNoGravity()) { this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.04D, 0.0D)); } @@ -19785,13 +19068,13 @@ index dfdf7e7fc1070975ec18fd215c724f4fc84d3705..52150e27294fcd51ae23ea3ff06673fb */ // Send position and velocity updates to nearby players on every tick while the TNT is in water. // This does pretty well at keeping their clients in sync with the server. -- net.minecraft.server.level.ChunkMap.TrackedEntity ete = ((net.minecraft.server.level.ServerLevel)this.level).getChunkSource().chunkMap.entityMap.get(this.getId()); +- net.minecraft.server.level.ChunkMap.TrackedEntity ete = ((net.minecraft.server.level.ServerLevel)this.level()).getChunkSource().chunkMap.entityMap.get(this.getId()); + net.minecraft.server.level.ChunkMap.TrackedEntity ete = this.tracker; // Folia - region threading if (ete != null) { net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket velocityPacket = new net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket(this); net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket positionPacket = new net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket(this); diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index 9976205537cfe228735687f1e9c52c74ac025690..f286abfff186b657db99f28d3592465ccee4498a 100644 +index 3f8c1d1d3c408fc4f15c4b5680bc22c86f104a9d..720278afd8c28c4303cc2fbfe5874a79d607f6ef 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java @@ -94,7 +94,7 @@ public class Zombie extends Monster { @@ -19805,7 +19088,7 @@ index 9976205537cfe228735687f1e9c52c74ac025690..f286abfff186b657db99f28d3592465c public Zombie(EntityType type, Level world) { @@ -217,10 +217,7 @@ public class Zombie extends Monster { public void tick() { - if (!this.level.isClientSide && this.isAlive() && !this.isNoAi()) { + if (!this.level().isClientSide && this.isAlive() && !this.isNoAi()) { if (this.isUnderWaterConverting()) { - // CraftBukkit start - Use wall time instead of ticks for conversion - int elapsedTicks = MinecraftServer.currentTick - this.lastTick; @@ -19834,7 +19117,7 @@ index 9976205537cfe228735687f1e9c52c74ac025690..f286abfff186b657db99f28d3592465c this.getEntityData().set(Zombie.DATA_DROWNED_CONVERSION_ID, true); } diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -index 71a36cf9b976443cca9ab63cd0eb23253f638562..c7a03304d8fb33e2e5c90547cba46665b51eec79 100644 +index 25ed5571b24e590bc95056020d84496492b53298..3bf69b19227dafc442b24120c252bf597025a1ac 100644 --- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java +++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java @@ -70,7 +70,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { @@ -19848,7 +19131,7 @@ index 71a36cf9b976443cca9ab63cd0eb23253f638562..c7a03304d8fb33e2e5c90547cba46665 super(type, world); @@ -145,10 +145,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { public void tick() { - if (!this.level.isClientSide && this.isAlive() && this.isConverting()) { + if (!this.level().isClientSide && this.isAlive() && this.isConverting()) { int i = this.getConversionProgress(); - // CraftBukkit start - Use wall time instead of ticks for villager conversion - int elapsedTicks = MinecraftServer.currentTick - this.lastTick; @@ -19868,7 +19151,7 @@ index 71a36cf9b976443cca9ab63cd0eb23253f638562..c7a03304d8fb33e2e5c90547cba46665 @Override diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -index ca96b893e22de3ae7c11d5cded51edf70bdcb6f2..6000e891620850aac303630bf6676085d41f65b5 100644 +index 564908ce0a560c2190fb624e77d227d3b7031024..1e1e79a9156d73299e27d70f76b39fd721346327 100644 --- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java @@ -213,10 +213,18 @@ public abstract class AbstractVillager extends AgeableMob implements InventoryCa @@ -19920,19 +19203,19 @@ index 5f407535298a31a34cfe114dd863fd6a9b977707..cb0f75fb32836efa50f0a86dfae7907b return 0; } else { diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java -index 6023b9eb3001e1a98ab8b970d853c4e7c7603f4d..0d16b7ea1e2c7d430b09721d50ea199aaa58bc89 100644 +index e30d5ae3e2900f43d7cafde71b8196f26e872841..bcc1af431fb2da84ba00e87ae9491eb5f580e6de 100644 --- a/src/main/java/net/minecraft/world/entity/npc/Villager.java +++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -201,7 +201,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler +@@ -202,7 +202,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler brain.setCoreActivities(ImmutableSet.of(Activity.CORE)); brain.setDefaultActivity(Activity.IDLE); brain.setActiveActivityIfPossible(Activity.IDLE); -- brain.updateActivityFromSchedule(this.level.getDayTime(), this.level.getGameTime()); -+ brain.updateActivityFromSchedule(this.level.getLevelData().getDayTime(), this.level.getLevelData().getGameTime()); // Folia - region threading +- brain.updateActivityFromSchedule(this.level().getDayTime(), this.level().getGameTime()); ++ brain.updateActivityFromSchedule(this.level().getLevelData().getDayTime(), this.level().getLevelData().getGameTime()); // Folia - region threading } @Override -@@ -722,6 +722,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler +@@ -728,6 +728,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler ServerLevel worldserver = minecraftserver.getLevel(globalpos.dimension()); if (worldserver != null) { @@ -19941,7 +19224,7 @@ index 6023b9eb3001e1a98ab8b970d853c4e7c7603f4d..0d16b7ea1e2c7d430b09721d50ea199a PoiManager villageplace = worldserver.getPoiManager(); Optional> optional = villageplace.getType(globalpos.pos()); BiPredicate> bipredicate = (BiPredicate) Villager.POI_MEMORIES.get(pos); -@@ -730,6 +732,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler +@@ -736,6 +738,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler villageplace.release(globalpos.pos()); DebugPackets.sendPoiTicketCountPacket(worldserver, globalpos.pos()); } @@ -20043,7 +19326,7 @@ index 5d199fe497bd852827d3d18fb7566a09e70331a3..db6139c04ce9a1bc17e4305f5644c0e1 entityvillagertrader.setWanderTarget(blockposition1); entityvillagertrader.restrictTo(blockposition1, 16); diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -index 5d6d26cfe8f0ab68a3145214b3fc126ca7a71a66..c8be8b54859e39967ede5c53ce940da39a68563e 100644 +index 7226be19248a1ffb8ff2c89b55882529d33a6c0c..b8c8a2ec4e5b5f1224a3d831ce0d4e4a2f4f8127 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java @@ -149,6 +149,11 @@ public abstract class AbstractArrow extends Projectile { @@ -20059,7 +19342,7 @@ index 5d6d26cfe8f0ab68a3145214b3fc126ca7a71a66..c8be8b54859e39967ede5c53ce940da3 Vec3 vec3d = this.getDeltaMovement(); diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java -index 2096e8a0bdbcfc865f175f3a01ab688542481531..8e072c1ae653258722fce7b50cd27076988d9666 100644 +index 6c9a8f062f989db022154155e8a05b334a0510da..fc7f6a9c32a87f6941826bd7d16d1be5bf4805c0 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java +++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java @@ -77,6 +77,11 @@ public abstract class AbstractHurtingProjectile extends Projectile { @@ -20075,7 +19358,7 @@ index 2096e8a0bdbcfc865f175f3a01ab688542481531..8e072c1ae653258722fce7b50cd27076 this.setSecondsOnFire(1); } diff --git a/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java b/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java -index d8a2b5e0e0ff640bcc827f1b1310bd19b9154cc2..2a4542a9bcb990e276796fb042d9f780cc7b63dd 100644 +index f349d64123686073b7f1b53a44be05fae5729587..99e16216346b7c19f7a33a5b170a3509189a2dbc 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java +++ b/src/main/java/net/minecraft/world/entity/projectile/EvokerFangs.java @@ -129,9 +129,9 @@ public class EvokerFangs extends Entity implements TraceableEntity { @@ -20091,7 +19374,7 @@ index d8a2b5e0e0ff640bcc827f1b1310bd19b9154cc2..2a4542a9bcb990e276796fb042d9f780 if (entityliving1.isAlliedTo((Entity) target)) { return; diff --git a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java -index fca27f98989bf106060ba08196255fe32f850df5..98c9eda4056093d8d230ac56ac407c39556ba2cb 100644 +index 288910fb168ddc5d3a61971778b8038a56772fa8..17dce984f28e7a47d28ec6d2c52af2d21864d8d5 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java +++ b/src/main/java/net/minecraft/world/entity/projectile/FireworkRocketEntity.java @@ -129,6 +129,11 @@ public class FireworkRocketEntity extends Projectile implements ItemSupplier { @@ -20131,7 +19414,7 @@ index fca27f98989bf106060ba08196255fe32f850df5..98c9eda4056093d8d230ac56ac407c39 } } diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java -index bcb0f3fd09ed064b64dc6495302b40828d906837..a39a35971748b38560cc58b6a0d9fde3b406a913 100644 +index b8c238287e0639b578170c6fec0d4db5a1a59fe7..98e322e8833ecadcf3a19b42215e658671f77733 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java +++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java @@ -104,7 +104,7 @@ public class FishingHook extends Projectile { @@ -20152,23 +19435,23 @@ index bcb0f3fd09ed064b64dc6495302b40828d906837..a39a35971748b38560cc58b6a0d9fde3 double d3 = vec3d.length(); diff --git a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java -index c4f4a26e016eea744f587461af80461074d48303..42f6481eb92a3aa91010c235f08ea6581e36573e 100644 +index 0bbe853f7df93f9dcd2b21d762939f8b6be069aa..4f4d6dedb99feac995e9b5bdf1d5c9f1332d61be 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java +++ b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java -@@ -30,6 +30,11 @@ public class LlamaSpit extends Projectile { +@@ -29,6 +29,11 @@ public class LlamaSpit extends Projectile { + @Override public void tick() { super.tick(); - Vec3 vec3d = this.getDeltaMovement(); + // Folia start - region threading - make sure entities do not move into regions they do not own -+ if (!io.papermc.paper.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)this.getLevel(), this.position(), this.getDeltaMovement(), 1)) { ++ if (!io.papermc.paper.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)this.level(), this.position(), this.getDeltaMovement(), 1)) { + return; + } + // Folia end - region threading - make sure entities do not move into regions they do not own - HitResult movingobjectposition = ProjectileUtil.getHitResult(this, this::canHitEntity); + Vec3 vec3d = this.getDeltaMovement(); + HitResult movingobjectposition = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); - this.preOnHit(movingobjectposition); // CraftBukkit - projectile hit event diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index a211ca048dddc75afce1f83ee1700bad66e457fc..7ef5520ed4f4a133449fe34c297d097a470a766f 100644 +index 1b7cf6d06bdf36f146656727511a461f2520762e..5835a1ba3e2927c6b5d143506b440ac5a43aaaa4 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java @@ -62,9 +62,20 @@ public abstract class Projectile extends Entity implements TraceableEntity { @@ -20192,7 +19475,7 @@ index a211ca048dddc75afce1f83ee1700bad66e457fc..7ef5520ed4f4a133449fe34c297d097a if (this.cachedOwner != null && !this.cachedOwner.isRemoved()) { this.refreshProjectileSource(false); // Paper return this.cachedOwner; -@@ -288,6 +299,6 @@ public abstract class Projectile extends Entity implements TraceableEntity { +@@ -289,6 +300,6 @@ public abstract class Projectile extends Entity implements TraceableEntity { public boolean mayInteract(Level world, BlockPos pos) { Entity entity = this.getOwner(); @@ -20201,7 +19484,7 @@ index a211ca048dddc75afce1f83ee1700bad66e457fc..7ef5520ed4f4a133449fe34c297d097a } } diff --git a/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java b/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java -index b9e4955fecabbad8d6762f3d933ea1402e932d9b..e026f2517a0714bba0f51aa2bf087c1babb55990 100644 +index 04c2ea1ff44af72ae48e2d6b7b912b1c14285038..8f1a80487802c64d0a10fbba6dae3d02b2cf15e6 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java +++ b/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java @@ -23,7 +23,7 @@ public class SmallFireball extends Fireball { @@ -20210,11 +19493,11 @@ index b9e4955fecabbad8d6762f3d933ea1402e932d9b..e026f2517a0714bba0f51aa2bf087c1b // CraftBukkit start - if (this.getOwner() != null && this.getOwner() instanceof Mob) { + if (owner != null && owner instanceof Mob) { // Folia - region threading - isIncendiary = this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); + isIncendiary = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); } // CraftBukkit end diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrowableProjectile.java b/src/main/java/net/minecraft/world/entity/projectile/ThrowableProjectile.java -index 88181c59e604ba3b132b9e695cef5eaf5b836029..0146b150b6cb70a7272e8d9781e100a012f93d9b 100644 +index ab777952bda1651796ed41e8a7fc6621f27db9aa..6b9365eba3339578ee2984605240b74db2b8a6c0 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/ThrowableProjectile.java +++ b/src/main/java/net/minecraft/world/entity/projectile/ThrowableProjectile.java @@ -44,6 +44,11 @@ public abstract class ThrowableProjectile extends Projectile { @@ -20222,15 +19505,15 @@ index 88181c59e604ba3b132b9e695cef5eaf5b836029..0146b150b6cb70a7272e8d9781e100a0 public void tick() { super.tick(); + // Folia start - region threading - make sure entities do not move into regions they do not own -+ if (!io.papermc.paper.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)this.getLevel(), this.position(), this.getDeltaMovement(), 1)) { ++ if (!io.papermc.paper.util.TickThread.isTickThreadFor((net.minecraft.server.level.ServerLevel)this.level(), this.position(), this.getDeltaMovement(), 1)) { + return; + } + // Folia end - region threading - make sure entities do not move into regions they do not own - HitResult movingobjectposition = ProjectileUtil.getHitResult(this, this::canHitEntity); + HitResult movingobjectposition = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); boolean flag = false; diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java -index 39ab9a283d856ba8d578d1378285758e32a24cf0..2b35495df87614c3578727598ebf1b4469829154 100644 +index e8114d89a3129e56c0329410a49ded63cc77cb4c..f1d291165fe6cb4160801d9bf2952e06a81287f9 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java +++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java @@ -43,6 +43,62 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { @@ -20244,7 +19527,7 @@ index 39ab9a283d856ba8d578d1378285758e32a24cf0..2b35495df87614c3578727598ebf1b44 + (Entity entity) -> { + // source is now an invalid reference, do not use it, use the entity parameter + -+ if (entity.getLevel() != checkWorld) { ++ if (entity.level() != checkWorld) { + // cannot teleport cross-world + return; + } @@ -20269,7 +19552,7 @@ index 39ab9a283d856ba8d578d1378285758e32a24cf0..2b35495df87614c3578727598ebf1b44 + // entity is now an invalid reference, do not use it, instead use teleported + if (teleported instanceof ServerPlayer player) { + // connection teleport is already done -+ ServerLevel world = player.getLevel(); ++ ServerLevel world = player.serverLevel(); + + // endermite spawn chance + if (world.random.nextFloat() < 0.05F && world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { @@ -20299,7 +19582,7 @@ index 39ab9a283d856ba8d578d1378285758e32a24cf0..2b35495df87614c3578727598ebf1b44 @@ -52,6 +108,20 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { } - if (!this.level.isClientSide && !this.isRemoved()) { + if (!this.level().isClientSide && !this.isRemoved()) { + // Folia start - region threading + if (true) { + // we can't fire events, because we do not actually know where the other entity is located @@ -20308,7 +19591,7 @@ index 39ab9a283d856ba8d578d1378285758e32a24cf0..2b35495df87614c3578727598ebf1b44 + } + Entity entity = this.getOwnerRaw(); + if (entity != null) { -+ attemptTeleport(entity, (ServerLevel)this.getLevel(), this.position()); ++ attemptTeleport(entity, (ServerLevel)this.level(), this.position()); + } + this.discard(); + return; @@ -20345,7 +19628,7 @@ index 39ab9a283d856ba8d578d1378285758e32a24cf0..2b35495df87614c3578727598ebf1b44 @Override public Entity changeDimension(ServerLevel destination) { diff --git a/src/main/java/net/minecraft/world/entity/raid/Raid.java b/src/main/java/net/minecraft/world/entity/raid/Raid.java -index 879c3bb661e24b9682b654def57c2800f4f8ca92..359f1690497eac00899eb26c17308e0a6fe943ad 100644 +index f7399737548483905f3b5c08a03876b0da54b714..113583d0d9de744e314bc7ee15cb8e21ec4a92f9 100644 --- a/src/main/java/net/minecraft/world/entity/raid/Raid.java +++ b/src/main/java/net/minecraft/world/entity/raid/Raid.java @@ -108,6 +108,13 @@ public class Raid { @@ -20381,20 +19664,20 @@ index 879c3bb661e24b9682b654def57c2800f4f8ca92..359f1690497eac00899eb26c17308e0a while (iterator.hasNext()) { ServerPlayer entityplayer = (ServerPlayer) iterator.next(); diff --git a/src/main/java/net/minecraft/world/entity/raid/Raider.java b/src/main/java/net/minecraft/world/entity/raid/Raider.java -index 99e9d46d42ddd0b451c6aeb847f1b295ebe5c697..9bc6b5a0b476e019e719709e68314d8e84d55a59 100644 +index 57fdcdaf54fd1c92a6e51a3a81789029096e5abe..c98e8a7d2aacf446a9770970b4d932d507db78b4 100644 --- a/src/main/java/net/minecraft/world/entity/raid/Raider.java +++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java @@ -91,7 +91,7 @@ public abstract class Raider extends PatrollingMonster { if (this.canJoinRaid()) { if (raid == null) { -- if (this.level.getGameTime() % 20L == 0L) { -+ if (this.level.getRedstoneGameTime() % 20L == 0L) { // Folia - region threading - Raid raid1 = ((ServerLevel) this.level).getRaidAt(this.blockPosition()); +- if (this.level().getGameTime() % 20L == 0L) { ++ if (this.level().getRedstoneGameTime() % 20L == 0L) { // Folia - region threading + Raid raid1 = ((ServerLevel) this.level()).getRaidAt(this.blockPosition()); if (raid1 != null && Raids.canJoinRaid(this, raid1)) { diff --git a/src/main/java/net/minecraft/world/entity/raid/Raids.java b/src/main/java/net/minecraft/world/entity/raid/Raids.java -index fabce3bc592b1b172b227395a07febdbb66ec3c9..ebdbadc1dba41f47ba4795ea43020b80102f5ccb 100644 +index 41457c9f27b18fa2734a6cca297ec5186470e82f..71a5315faf800c2b42da36639b106c41b3dc5e8a 100644 --- a/src/main/java/net/minecraft/world/entity/raid/Raids.java +++ b/src/main/java/net/minecraft/world/entity/raid/Raids.java @@ -28,14 +28,14 @@ import net.minecraft.world.phys.Vec3; @@ -20461,12 +19744,12 @@ index fabce3bc592b1b172b227395a07febdbb66ec3c9..ebdbadc1dba41f47ba4795ea43020b80 + return false; + } + // Folia end - make raids thread-safe - return raider != null && raid != null && raid.getLevel() != null ? raider.isAlive() && raider.canJoinRaid() && raider.getNoActionTime() <= 2400 && raider.level.dimensionType() == raid.getLevel().dimensionType() : false; + return raider != null && raid != null && raid.getLevel() != null ? raider.isAlive() && raider.canJoinRaid() && raider.getNoActionTime() <= 2400 && raider.level().dimensionType() == raid.getLevel().dimensionType() : false; } @@ -82,7 +99,7 @@ public class Raids extends SavedData { } else { - DimensionType dimensionmanager = player.level.dimensionType(); + DimensionType dimensionmanager = player.level().dimensionType(); - if (!dimensionmanager.hasRaids()) { + if (!dimensionmanager.hasRaids() || !io.papermc.paper.util.TickThread.isTickThreadFor(this.level, player.chunkPosition().x, player.chunkPosition().z, 8)) { // Folia - region threading @@ -20513,10 +19796,10 @@ index fabce3bc592b1b172b227395a07febdbb66ec3c9..ebdbadc1dba41f47ba4795ea43020b80 if (raid1.isActive() && d1 < d0) { diff --git a/src/main/java/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java b/src/main/java/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java -index 1fe630118981b02092054af3c5c6227a889ee3c2..57ecc5e506c895fe701cb24c9b503d6009602a26 100644 +index 48b4fe75a7f881e7713885d79d4ef5ec7b574a2d..1e1adbb68596bd8351ec77da67e6fd8139734b61 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java -@@ -145,5 +145,11 @@ public class MinecartCommandBlock extends AbstractMinecart { +@@ -150,5 +150,11 @@ public class MinecartCommandBlock extends AbstractMinecart { return (org.bukkit.craftbukkit.entity.CraftMinecartCommand) MinecartCommandBlock.this.getBukkitEntity(); } // CraftBukkit end @@ -20529,7 +19812,7 @@ index 1fe630118981b02092054af3c5c6227a889ee3c2..57ecc5e506c895fe701cb24c9b503d60 } } diff --git a/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java b/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java -index 1b8f22805af87dc08e0dea9fd93a5f93c0b05107..00bc99948ddd67bb85c3797f869064540a8b1213 100644 +index fc35cfc9d045f3e5b6a50af1d0ba83b6e322091f..c87765a28f31673d547653b293c48856511bc693 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/MinecartHopper.java @@ -128,7 +128,7 @@ public class MinecartHopper extends AbstractMinecartContainer implements Hopper @@ -20555,10 +19838,10 @@ index d7a0cbde8f8c99276307502674c71463fbe7e89c..2a501b3fa8d69f627b279fd035fd2cb1 } diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index d81fcbadc5c0b3d4b54dde5d47a0f847d8ec6918..b9f6a441a67acaa4478398219f68446c86781351 100644 +index b367ec1feaccbd67e9e28c0d0515e163f37135fe..3cd24d49dc32ed539a05181a1c9973886f4d65cc 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -336,6 +336,7 @@ public final class ItemStack { +@@ -340,6 +340,7 @@ public final class ItemStack { } public InteractionResult useOn(UseOnContext itemactioncontext, InteractionHand enumhand) { // CraftBukkit - add hand @@ -20566,7 +19849,7 @@ index d81fcbadc5c0b3d4b54dde5d47a0f847d8ec6918..b9f6a441a67acaa4478398219f68446c net.minecraft.world.entity.player.Player entityhuman = itemactioncontext.getPlayer(); BlockPos blockposition = itemactioncontext.getClickedPos(); BlockInWorld shapedetectorblock = new BlockInWorld(itemactioncontext.getLevel(), blockposition, false); -@@ -347,12 +348,13 @@ public final class ItemStack { +@@ -351,12 +352,13 @@ public final class ItemStack { CompoundTag oldData = this.getTagClone(); int oldCount = this.getCount(); ServerLevel world = (ServerLevel) itemactioncontext.getLevel(); @@ -20582,7 +19865,7 @@ index d81fcbadc5c0b3d4b54dde5d47a0f847d8ec6918..b9f6a441a67acaa4478398219f68446c } } Item item = this.getItem(); -@@ -361,14 +363,14 @@ public final class ItemStack { +@@ -365,14 +367,14 @@ public final class ItemStack { int newCount = this.getCount(); this.setCount(oldCount); this.setTagClone(oldData); @@ -20604,7 +19887,7 @@ index d81fcbadc5c0b3d4b54dde5d47a0f847d8ec6918..b9f6a441a67acaa4478398219f68446c StructureGrowEvent structureEvent = null; if (treeType != null) { boolean isBonemeal = this.getItem() == Items.BONE_MEAL; -@@ -396,12 +398,12 @@ public final class ItemStack { +@@ -400,12 +402,12 @@ public final class ItemStack { SignItem.openSign = null; // SPIGOT-6758 - Reset on early return return enuminteractionresult; } @@ -20620,7 +19903,7 @@ index d81fcbadc5c0b3d4b54dde5d47a0f847d8ec6918..b9f6a441a67acaa4478398219f68446c if (blocks.size() > 1) { placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(world, entityhuman, enumhand, blocks, blockposition.getX(), blockposition.getY(), blockposition.getZ()); } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - don't call event twice for snow buckets -@@ -412,13 +414,13 @@ public final class ItemStack { +@@ -416,13 +418,13 @@ public final class ItemStack { enuminteractionresult = InteractionResult.FAIL; // cancel placement // PAIL: Remove this when MC-99075 fixed placeEvent.getPlayer().updateInventory(); @@ -20637,7 +19920,7 @@ index d81fcbadc5c0b3d4b54dde5d47a0f847d8ec6918..b9f6a441a67acaa4478398219f68446c // Brute force all possible updates BlockPos placedPos = ((CraftBlock) placeEvent.getBlock()).getPosition(); -@@ -433,7 +435,7 @@ public final class ItemStack { +@@ -437,7 +439,7 @@ public final class ItemStack { this.setCount(newCount); } @@ -20646,7 +19929,7 @@ index d81fcbadc5c0b3d4b54dde5d47a0f847d8ec6918..b9f6a441a67acaa4478398219f68446c world.setBlockEntity(e.getValue()); } -@@ -512,8 +514,8 @@ public final class ItemStack { +@@ -524,8 +526,8 @@ public final class ItemStack { entityhuman.awardStat(Stats.ITEM_USED.get(item)); } } @@ -20658,7 +19941,7 @@ index d81fcbadc5c0b3d4b54dde5d47a0f847d8ec6918..b9f6a441a67acaa4478398219f68446c return enuminteractionresult; diff --git a/src/main/java/net/minecraft/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java -index 586852e347cfeb6e52d16e51b3f193e814036e81..eacd5b6f649a59f845cc8d9d9373d41ed3757c97 100644 +index d3c29e6bf8b3c2dd628809177dac50220a7de415..fbb4a46c0da5629d6bd0afee94e5bdc9ab619873 100644 --- a/src/main/java/net/minecraft/world/item/MapItem.java +++ b/src/main/java/net/minecraft/world/item/MapItem.java @@ -103,6 +103,7 @@ public class MapItem extends ComplexItem { @@ -20672,9 +19955,9 @@ index 586852e347cfeb6e52d16e51b3f193e814036e81..eacd5b6f649a59f845cc8d9d9373d41e @@ -134,9 +135,9 @@ public class MapItem extends ComplexItem { int j2 = (j / i + k1 - 64) * i; int k2 = (k / i + l1 - 64) * i; - Multiset multiset = LinkedHashMultiset.create(); + Multiset multiset = LinkedHashMultiset.create(); - LevelChunk chunk = world.getChunkIfLoaded(SectionPos.blockToSectionCoord(j2), SectionPos.blockToSectionCoord(k2)); // Paper - Maps shouldn't load chunks -+ LevelChunk chunk = world.getChunkIfLoaded(SectionPos.blockToSectionCoord(j2), SectionPos.blockToSectionCoord(k2)); // Paper - Maps shouldn't load chunks // Folia - super important this remains true ++ LevelChunk chunk = world.getChunkIfLoaded(SectionPos.blockToSectionCoord(j2), SectionPos.blockToSectionCoord(k2)); // Paper - Maps shouldn't load chunks // Folia - super important that it uses getChunkIfLoaded - if (chunk != null && !chunk.isEmpty()) { // Paper - Maps shouldn't load chunks + if (chunk != null && !chunk.isEmpty() && io.papermc.paper.util.TickThread.isTickThreadFor((ServerLevel)world, chunk.getPos())) { // Paper - Maps shouldn't load chunks // Folia - make sure chunk is owned @@ -20735,7 +20018,7 @@ index c6d2f764efa9b8bec730bbe757d480e365b25ccc..af9313a3b3aaa0af4f2a2f4fb2424dc3 } diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -index 888936385196a178ab8b730fd5e4fff4a5466428..a49621dcc5915c86bf0327a4903706817c7b8ff4 100644 +index ceeedbd88c56c08ec8b047c9ca2f14cc581e12ad..19cb22df8eb29d1708e3da2124de3b43378b575a 100644 --- a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +++ b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java @@ -20,7 +20,7 @@ import net.minecraft.world.phys.Vec3; @@ -20865,7 +20148,7 @@ index 3b959f42d958bf0f426853aee56753d6c455fcdb..b1a6a66ed02706c1adc36dcedfa415f5 return player; } diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index 185f7b1d4df59f5db7b85b529a2de6402630bf35..4c75058c400ec8d5824ac53beba5da53c94aee8b 100644 +index 8f97c9df726ac20cfce7bdddd5dd4f8c5aa76c35..e8c4815960ab144298d4352f393b9670e7004e62 100644 --- a/src/main/java/net/minecraft/world/level/Explosion.java +++ b/src/main/java/net/minecraft/world/level/Explosion.java @@ -246,7 +246,7 @@ public class Explosion { @@ -20907,10 +20190,10 @@ index 185f7b1d4df59f5db7b85b529a2de6402630bf35..4c75058c400ec8d5824ac53beba5da53 static class CacheKey { diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef84a95a91b 100644 +index 7771748a15c153352ce4874495f5fa7437aaf9a7..f99b5a14717f87fe0629cbe7b6339a5ff2bb5fe9 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -118,10 +118,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -117,10 +117,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public static final int TICKS_PER_DAY = 24000; public static final int MAX_ENTITY_SPAWN_Y = 20000000; public static final int MIN_ENTITY_SPAWN_Y = -20000000; @@ -20925,7 +20208,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 public final Thread thread; private final boolean isDebug; private int skyDarken; -@@ -131,7 +131,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -130,7 +130,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public float rainLevel; protected float oThunderLevel; public float thunderLevel; @@ -20934,7 +20217,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 /** @deprecated */ @Deprecated private final RandomSource threadSafeRandom = RandomSource.createThreadSafe(); -@@ -145,7 +145,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -144,7 +144,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { private final ResourceKey dimension; private final RegistryAccess registryAccess; private final DamageSources damageSources; @@ -20943,7 +20226,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 // CraftBukkit start Added the following private final CraftWorld world; -@@ -154,20 +154,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -153,20 +153,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public org.bukkit.generator.ChunkGenerator generator; public static final boolean DEBUG_ENTITIES = Boolean.getBoolean("debug.entities"); // Paper @@ -20967,7 +20250,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 public final org.spigotmc.SpigotWorldConfig spigotConfig; // Spigot // Paper start private final io.papermc.paper.configuration.WorldConfiguration paperConfig; -@@ -181,9 +171,9 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -179,9 +169,9 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public static BlockPos lastPhysicsProblem; // Spigot private org.spigotmc.TickLimiter entityLimiter; private org.spigotmc.TickLimiter tileLimiter; @@ -20980,7 +20263,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 // Paper start - fix and optimise world upgrading // copied from below -@@ -227,7 +217,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -225,7 +215,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { List ret = new java.util.ArrayList<>(); double maxRangeSquared = maxRange * maxRange; @@ -20989,7 +20272,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 if ((maxRange < 0.0 || player.distanceToSqr(sourceX, sourceY, sourceZ) < maxRangeSquared)) { if (predicate == null || predicate.test(player)) { ret.add(player); -@@ -243,7 +233,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -241,7 +231,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { net.minecraft.server.level.ServerPlayer closest = null; double closestRangeSquared = maxRange < 0.0 ? Double.MAX_VALUE : maxRange * maxRange; @@ -20998,7 +20281,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 double distanceSquared = player.distanceToSqr(sourceX, sourceY, sourceZ); if (distanceSquared < closestRangeSquared && (predicate == null || predicate.test(player))) { closest = player; -@@ -274,6 +264,33 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -272,6 +262,33 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public abstract ResourceKey getTypeKey(); @@ -21009,7 +20292,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 + io.papermc.paper.threadedregions.RegionizedWorldData.REGION_CALLBACK + ); + public volatile io.papermc.paper.threadedregions.RegionizedServer.WorldLevelData tickData; -+ public final java.util.concurrent.ConcurrentHashMap.KeySetView needsChangeBroadcasting = java.util.concurrent.ConcurrentHashMap.newKeySet(); ++ public final java.util.concurrent.ConcurrentHashMap.KeySetView needsChangeBroadcasting = java.util.concurrent.ConcurrentHashMap.newKeySet(); + + public io.papermc.paper.threadedregions.RegionizedWorldData getCurrentWorldData() { + final io.papermc.paper.threadedregions.RegionizedWorldData ret = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); @@ -21029,10 +20312,10 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 + } + // Folia end - region ticking + - protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - Async-Anti-Xray - Pass executor + protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator) { // Paper this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper -@@ -317,7 +334,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -315,7 +332,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { this.thread = Thread.currentThread(); this.biomeManager = new BiomeManager(this, i); this.isDebug = flag1; @@ -21041,7 +20324,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 this.registryAccess = iregistrycustom; this.damageSources = new DamageSources(iregistrycustom); // CraftBukkit start -@@ -458,8 +475,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -455,8 +472,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { @Nullable public final BlockState getBlockStateIfLoaded(BlockPos pos) { // CraftBukkit start - tree generation @@ -21052,7 +20335,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 if (previous != null) { return previous.getHandle(); } -@@ -521,16 +538,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -518,16 +535,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable { @Override public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) { @@ -21073,7 +20356,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 } blockstate.setFlag(flags); // Paper - update the flag also blockstate.setData(state); -@@ -547,10 +565,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -544,10 +562,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { // CraftBukkit start - capture blockstates boolean captured = false; @@ -21086,7 +20369,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 captured = true; } // CraftBukkit end -@@ -560,8 +578,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -556,8 +574,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { if (iblockdata1 == null) { // CraftBukkit start - remove blockstate if failed (or the same) @@ -21097,7 +20380,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 } // CraftBukkit end return false; -@@ -604,7 +622,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -594,7 +612,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { */ // CraftBukkit start @@ -21106,7 +20389,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 // Modularize client and physic updates // Spigot start try { -@@ -653,7 +671,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -643,7 +661,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { // CraftBukkit start iblockdata1.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); // Don't call an event for the old block to limit event spam CraftWorld world = ((ServerLevel) this).getWorld(); @@ -21115,7 +20398,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftBlockData.fromData(iblockdata)); this.getCraftServer().getPluginManager().callEvent(event); -@@ -667,7 +685,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -657,7 +675,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } // CraftBukkit start - SPIGOT-5710 @@ -21124,7 +20407,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 this.onBlockStateChange(blockposition, iblockdata1, iblockdata2); } // CraftBukkit end -@@ -746,7 +764,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -736,7 +754,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { @Override public void neighborShapeChanged(Direction direction, BlockState neighborState, BlockPos pos, BlockPos neighborPos, int flags, int maxUpdateDepth) { @@ -21133,7 +20416,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 } @Override -@@ -771,11 +789,34 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -761,11 +779,34 @@ public abstract class Level implements LevelAccessor, AutoCloseable { return this.getChunkSource().getLightEngine(); } @@ -21170,7 +20453,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 if (previous != null) { return previous.getHandle(); } -@@ -866,7 +907,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -856,7 +897,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } public void addBlockEntityTicker(TickingBlockEntity ticker) { @@ -21179,7 +20462,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 } protected void tickBlockEntities() { -@@ -874,11 +915,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -864,11 +905,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { gameprofilerfiller.push("blockEntities"); timings.tileEntityPending.startTiming(); // Spigot @@ -21195,7 +20478,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 timings.tileEntityPending.stopTiming(); // Spigot timings.tileEntityTick.startTiming(); // Spigot -@@ -887,9 +927,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -877,9 +917,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { int tilesThisCycle = 0; var toRemove = new it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet(net.minecraft.Util.identityStrategy()); // Paper - use removeAll toRemove.add(null); @@ -21207,7 +20490,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 // Spigot start if (tickingblockentity == null) { this.getCraftServer().getLogger().severe("Spigot has detected a null entity and has removed it, preventing a crash"); -@@ -906,19 +945,19 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -896,19 +935,19 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } else if (this.shouldTickBlocksAt(tickingblockentity.getPos())) { tickingblockentity.tick(); // Paper start - execute chunk tasks during tick @@ -21232,8 +20515,8 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 } public void guardEntityTick(Consumer tickConsumer, T entity) { -@@ -931,7 +970,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level.getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); +@@ -921,7 +960,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); MinecraftServer.LOGGER.error(msg, throwable); getCraftServer().getPluginManager().callEvent(new ServerExceptionEvent(new ServerInternalException(msg, throwable))); - entity.discard(); @@ -21242,7 +20525,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 // Paper end } } -@@ -1014,9 +1054,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -1004,9 +1044,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { @Nullable public BlockEntity getBlockEntity(BlockPos blockposition, boolean validate) { @@ -21258,7 +20541,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 return blockEntity; } // Paper end -@@ -1029,8 +1074,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -1019,8 +1064,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { if (!this.isOutsideBuildHeight(blockposition)) { // CraftBukkit start @@ -21269,7 +20552,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 return; } // CraftBukkit end -@@ -1110,6 +1155,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -1100,6 +1145,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { @Override public List getEntities(@Nullable Entity except, AABB box, Predicate predicate) { @@ -21277,7 +20560,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 this.getProfiler().incrementCounter("getEntities"); List list = Lists.newArrayList(); ((ServerLevel)this).getEntityLookup().getEntities(except, box, list, predicate); // Paper - optimise this call -@@ -1129,6 +1175,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -1119,6 +1165,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } public void getEntities(EntityTypeTest filter, AABB box, Predicate predicate, List result, int limit) { @@ -21285,7 +20568,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 this.getProfiler().incrementCounter("getEntities"); // Paper start - optimise this call //TODO use limit -@@ -1234,13 +1281,30 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -1156,13 +1203,30 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public void disconnect() {} @@ -21318,7 +20601,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 public boolean mayInteract(Player player, BlockPos pos) { return true; -@@ -1442,8 +1506,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -1364,8 +1428,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } public final BlockPos.MutableBlockPos getRandomBlockPosition(int x, int y, int z, int l, BlockPos.MutableBlockPos out) { // Paper end @@ -21328,7 +20611,7 @@ index 944da18bcc993ab0488a34cbbe9df134c355301a..32b9358bacabedf4513bb17c68200ef8 out.set(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); // Paper - change to setValues call return out; // Paper -@@ -1474,7 +1537,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -1396,7 +1459,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { @Override public long nextSubTickCount() { @@ -21367,10 +20650,10 @@ index 73d1adc5ddf0363966eac0c77c8dfbbb20a2b6a3..375a2b57bcb29458443c1a4e2be3c0e5 default void scheduleTick(BlockPos pos, Block block, int delay, TickPriority priority) { diff --git a/src/main/java/net/minecraft/world/level/LevelReader.java b/src/main/java/net/minecraft/world/level/LevelReader.java -index 7fe1b8856bf916796fa6d2a984f0a07a2331e23b..07802d0a25e49519c3c9b33c217e05002cf05e31 100644 +index e3e2b88b8ade4fa2b482626c7e00ac6a0bf8eb5e..7895c39a051aa561f8acf749b58b13d53b63d5ba 100644 --- a/src/main/java/net/minecraft/world/level/LevelReader.java +++ b/src/main/java/net/minecraft/world/level/LevelReader.java -@@ -135,6 +135,15 @@ public interface LevelReader extends BlockAndTintGetter, CollisionGetter, BiomeM +@@ -140,6 +140,15 @@ public interface LevelReader extends BlockAndTintGetter, CollisionGetter, Signal return this.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); } @@ -21386,7 +20669,7 @@ index 7fe1b8856bf916796fa6d2a984f0a07a2331e23b..07802d0a25e49519c3c9b33c217e0500 default ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus status) { return this.getChunk(chunkX, chunkZ, status, true); } -@@ -204,6 +213,25 @@ public interface LevelReader extends BlockAndTintGetter, CollisionGetter, BiomeM +@@ -209,6 +218,25 @@ public interface LevelReader extends BlockAndTintGetter, CollisionGetter, Signal return maxY >= this.getMinBuildHeight() && minY < this.getMaxBuildHeight() ? this.hasChunksAt(minX, minZ, maxX, maxZ) : false; } @@ -21413,7 +20696,7 @@ index 7fe1b8856bf916796fa6d2a984f0a07a2331e23b..07802d0a25e49519c3c9b33c217e0500 @Deprecated default boolean hasChunksAt(int minX, int minZ, int maxX, int maxZ) { diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index 15d266fc97eb73338f4f6fb2cfe25d6861e79810..52a7d5ef4a25bee618d9c9a784df95a2341fe015 100644 +index 089dd93d4cd4c1f72e63c4944b3b82c1e2ba732d..8903a2db824377c2c3232d02f075f4e267a64dbd 100644 --- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java +++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java @@ -115,11 +115,7 @@ public final class NaturalSpawner { @@ -21544,10 +20827,10 @@ index 09c85ed428b8eaf51f8b3c6e45cce925f05ab354..3d797880b5964dd07f3757495ea1255a } diff --git a/src/main/java/net/minecraft/world/level/block/BedBlock.java b/src/main/java/net/minecraft/world/level/block/BedBlock.java -index d1d5363ab1742add8ff45507a303106f4d65f52f..372e2c8c693fd77d7ff37bf4fbd24eb0e275911f 100644 +index d40500f9a807cab0b2fb6fa9032f33f4fb74c895..824d89c2c70c64f6b37155dcc2aa0f7bd34d0303 100644 --- a/src/main/java/net/minecraft/world/level/block/BedBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java -@@ -363,7 +363,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock +@@ -357,7 +357,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock world.setBlock(blockposition1, (BlockState) state.setValue(BedBlock.PART, BedPart.HEAD), 3); // CraftBukkit start - SPIGOT-7315: Don't updated if we capture block states @@ -21557,10 +20840,10 @@ index d1d5363ab1742add8ff45507a303106f4d65f52f..372e2c8c693fd77d7ff37bf4fbd24eb0 } // CraftBukkit end diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java -index 4f91e4832a94c3facbc711fcae4cb5ad540a5ca0..e5ca38b375becfb3a10fb94739bdaed354c426c6 100644 +index 9522e646529f3d849471931b4b3c0d133e7fcfc5..52dda8e8074e0f894d9ef95b46bd937dae629f14 100644 --- a/src/main/java/net/minecraft/world/level/block/Block.java +++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -394,8 +394,8 @@ public class Block extends BlockBehaviour implements ItemLike { +@@ -382,8 +382,8 @@ public class Block extends BlockBehaviour implements ItemLike { entityitem.setDefaultPickUpDelay(); // CraftBukkit start @@ -21585,10 +20868,10 @@ index 03fde6e47c4a347c62fe9b4a3351769aedf874f6..d2e3e1d20d60f5edd0d93709b808f812 } } diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java -index 7579946ce222b6ab3685a7fd9821bcd5a4babe33..08f0360e12087cdce0671a0f3f782b62a75bac47 100644 +index 0003fb51ae3a6575575e10b4c86719f3061e2577..362aab7977f636fa8a8548a01ab333da066fbc76 100644 --- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java -@@ -117,9 +117,9 @@ public class CactusBlock extends Block { +@@ -115,9 +115,9 @@ public class CactusBlock extends Block { @Override public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper @@ -21630,10 +20913,10 @@ index 81376e725151f723dad8a7b5c1a4bd597e60294e..a9876256edd5354015e83d943a83d1c0 } diff --git a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -index 8f55d0753fa26924235c943595f0d1a06a933a6f..a195d37847e3278d7721641b5db858913c0afe46 100644 +index 4720b884e1e311f44a012f1219fe648a411247b4..911dc79ea7c6343273ecfe8fa049c115a4950654 100644 --- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java +++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java -@@ -47,7 +47,7 @@ public class DispenserBlock extends BaseEntityBlock { +@@ -46,7 +46,7 @@ public class DispenserBlock extends BaseEntityBlock { object2objectopenhashmap.defaultReturnValue(new DefaultDispenseItemBehavior()); }); private static final int TRIGGER_DURATION = 4; @@ -21642,7 +20925,7 @@ index 8f55d0753fa26924235c943595f0d1a06a933a6f..a195d37847e3278d7721641b5db85891 public static void registerBehavior(ItemLike provider, DispenseItemBehavior behavior) { DispenserBlock.DISPENSER_REGISTRY.put(provider.asItem(), behavior); -@@ -94,7 +94,7 @@ public class DispenserBlock extends BaseEntityBlock { +@@ -93,7 +93,7 @@ public class DispenserBlock extends BaseEntityBlock { if (idispensebehavior != DispenseItemBehavior.NOOP) { if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockPreDispenseEvent(world, pos, itemstack, i)) return; // Paper - BlockPreDispenseEvent is called here @@ -21681,16 +20964,16 @@ index 9d0fc6b5b8d8fb31cacf7e8b346b9babf1a3e8a2..85701eba8dea71c64f92d5eaf99cb232 // CraftBukkit end ((ConfiguredFeature) holder.value()).place(world, world.getChunkSource().getGenerator(), random, pos); diff --git a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java -index 29efe50dd3911a1d7ef5175034e82191130b8fd1..83810585125403a8ed1f85ff3dfc0f2d6ec40c75 100644 +index 745f33ce496a7ce8c788f24c093b37933a74148a..b81b43635dcf516492ec5edfa385efcdea4f7534 100644 --- a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java +++ b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java @@ -80,7 +80,7 @@ public class HoneyBlock extends HalfTransparentBlock { } private void maybeDoSlideAchievement(Entity entity, BlockPos pos) { -- if (entity instanceof ServerPlayer && entity.level.getGameTime() % 20L == 0L) { -+ if (entity instanceof ServerPlayer && entity.level.getRedstoneGameTime() % 20L == 0L) { // Folia - region threading - CriteriaTriggers.HONEY_BLOCK_SLIDE.trigger((ServerPlayer)entity, entity.level.getBlockState(pos)); +- if (entity instanceof ServerPlayer && entity.level().getGameTime() % 20L == 0L) { ++ if (entity instanceof ServerPlayer && entity.level().getRedstoneGameTime() % 20L == 0L) { // Folia - region threading + CriteriaTriggers.HONEY_BLOCK_SLIDE.trigger((ServerPlayer)entity, entity.level().getBlockState(pos)); } diff --git a/src/main/java/net/minecraft/world/level/block/LightningRodBlock.java b/src/main/java/net/minecraft/world/level/block/LightningRodBlock.java @@ -21707,10 +20990,10 @@ index da3b301a42a93c891d083a6e02d1be8ed35adf1d..f354981843868bf938be0b5ac1ef2ce3 } } diff --git a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java -index 12ffb5714f088f4aeafa1ad6a36f5b64a86c4c96..57eceff3fd313449c6db02c7719ad9f74ed15b17 100644 +index 1b766045687e4dcded5cbcc50b746c55b9a34e22..a8270d65c22fa979ce53ffeeeadce8d61db397e1 100644 --- a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java +++ b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java -@@ -28,9 +28,9 @@ public class MagmaBlock extends Block { +@@ -23,9 +23,9 @@ public class MagmaBlock extends Block { @Override public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) { if (!entity.isSteppingCarefully() && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity) entity)) { @@ -21736,10 +21019,10 @@ index f6f8e155223cba10c4073ddca602d1aa3aa872d7..bc42faf2f5cf54197849b1ad133a8851 return true; } else { diff --git a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -index 6b909d41ccdf6c1ac3ac0c4e673ff52f0d14a238..e7079e164f5943f21aff487c22c525f7bf98911f 100644 +index e978132e51cde52f7ff1ba31ad521fc2cb4f0dce..d10afb2eee3aeb606c94ab348853aac489fada2e 100644 --- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java +++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -@@ -143,9 +143,9 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate +@@ -141,9 +141,9 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate @Override public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { if (state.getValue(PointedDripstoneBlock.TIP_DIRECTION) == Direction.UP && state.getValue(PointedDripstoneBlock.THICKNESS) == DripstoneThickness.TIP) { @@ -21752,7 +21035,7 @@ index 6b909d41ccdf6c1ac3ac0c4e673ff52f0d14a238..e7079e164f5943f21aff487c22c525f7 super.fallOn(world, state, pos, entity, fallDistance); } diff --git a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java -index 5ea09cc455bd86beb450f0e0275d7c6c8da98084..cc019091a2decbc1e5c760129778b42ea1d449d6 100644 +index 70544dac18381ab3fa8ddfa7d276a4ef03f9b191..858f81db707a73edeec2bb4ee62e69ad2f64200c 100644 --- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java +++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java @@ -67,7 +67,7 @@ public class RedStoneWireBlock extends Block { @@ -21968,7 +21251,7 @@ index d5ec1e5909c03a58add7f4597b140f787600c09a..f230d39da84304fa19bd62522b175899 if (treeType != null) { event = new StructureGrowEvent(location, treeType, false, null, blocks); diff --git a/src/main/java/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java b/src/main/java/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java -index af46c05a34292d271fd4a809398e6b299e10b12b..2eeb9352bc35a31efe98be51746014afd7e5e71c 100644 +index 9bbb9f8e917288bb0d11661a1399a05631ebcce0..8d57dc0aa214e14690880f9a58234fff134cf1f1 100644 --- a/src/main/java/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java @@ -51,7 +51,7 @@ public abstract class SpreadingSnowyDirtBlock extends SnowyDirtBlock { @@ -21997,10 +21280,10 @@ index 08a11888133b97e52535cb49cad218a1b6c6ac97..9d9564d603d5b05e60dc48d85039c384 } diff --git a/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java b/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java -index b91effe91dad2e1aeea0ea31140f7432833b343f..46979b4ee8c24b499577aa64167c759664148240 100644 +index 1aa0e921890d600c9274deb923da04e72b12bcc6..daca759793b4a22406fb9c75b5ac9814920d3ac0 100644 --- a/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java +++ b/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java -@@ -53,7 +53,7 @@ public class WitherSkullBlock extends SkullBlock { +@@ -51,7 +51,7 @@ public class WitherSkullBlock extends SkullBlock { } public static void checkSpawn(Level world, BlockPos pos, SkullBlockEntity blockEntity) { @@ -22010,7 +21293,7 @@ index b91effe91dad2e1aeea0ea31140f7432833b343f..46979b4ee8c24b499577aa64167c7596 BlockState iblockdata = blockEntity.getBlockState(); boolean flag = iblockdata.is(Blocks.WITHER_SKELETON_SKULL) || iblockdata.is(Blocks.WITHER_SKELETON_WALL_SKULL); diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -index ef740d1ad6352ca4af299001a081b720bc472d2e..cf41ff83b1465b89e96d029b72530cdba10f3d99 100644 +index 59246e24558569f7f50b4d4d508616798091c888..dfd3432d648c9dafcb45b4f6a00d58cb308625b3 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java @@ -202,7 +202,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name @@ -22023,7 +21306,7 @@ index ef740d1ad6352ca4af299001a081b720bc472d2e..cf41ff83b1465b89e96d029b72530cdb blockEntity.levels = BeaconBlockEntity.updateBase(world, i, j, k); } diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -index 1b248db497500aa6bd346b306dcb908af77626f3..ac1f6d5c78c1970b3242c017031679fb9a906fb0 100644 +index 370a25d2deb54f10a35ee24d9e7e92fbfde60edf..fb16bbfd8db86495eced3e9ff9f9c4267e2230cc 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java @@ -26,7 +26,7 @@ import co.aikar.timings.MinecraftTimings; // Paper @@ -22035,7 +21318,7 @@ index 1b248db497500aa6bd346b306dcb908af77626f3..ac1f6d5c78c1970b3242c017031679fb public Timing tickTimer = MinecraftTimings.getTileEntityTimings(this); // Paper // CraftBukkit start - data containers -@@ -42,6 +42,12 @@ public abstract class BlockEntity { +@@ -41,6 +41,12 @@ public abstract class BlockEntity { protected boolean remove; private BlockState blockState; @@ -22048,7 +21331,7 @@ index 1b248db497500aa6bd346b306dcb908af77626f3..ac1f6d5c78c1970b3242c017031679fb public BlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { this.type = type; this.worldPosition = pos.immutable(); -@@ -163,7 +169,7 @@ public abstract class BlockEntity { +@@ -162,7 +168,7 @@ public abstract class BlockEntity { public void setChanged() { if (this.level != null) { @@ -22085,22 +21368,23 @@ index c57efcb9a79337ec791e4e8f6671612f0a82b441..526d1bfd5ad0de7bcfd0c2da902515f3 // CraftBukkit end diff --git a/src/main/java/net/minecraft/world/level/block/entity/CommandBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CommandBlockEntity.java -index a33d98a3ab2d00256e3c85b3de3680b4765df8d3..b311d150b0e4f1e36ba22042b02d8acd2cd5ce8c 100644 +index 167f334eec90417eba05fcecec21435415771df7..3698bae85063baec031a61d24ef3286703a9d04c 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/CommandBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/CommandBlockEntity.java -@@ -56,6 +56,12 @@ public class CommandBlockEntity extends BlockEntity { - +@@ -57,6 +57,13 @@ public class CommandBlockEntity extends BlockEntity { return new CommandSourceStack(this, Vec3.atCenterOf(CommandBlockEntity.this.worldPosition), new Vec2(0.0F, enumdirection.toYRot()), this.getLevel(), 2, this.getName().getString(), this.getName(), this.getLevel().getServer(), (Entity) null); } + + // Folia start + @Override + public void threadCheck() { + io.papermc.paper.util.TickThread.ensureTickThread((ServerLevel) CommandBlockEntity.this.level, CommandBlockEntity.this.worldPosition, "Asynchronous sendSystemMessage to a command block"); + } + // Folia end - }; - - public CommandBlockEntity(BlockPos pos, BlockState state) { ++ + @Override + public boolean isValid() { + return !CommandBlockEntity.this.isRemoved(); diff --git a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java index 963a596154091b79ca139af6274aa323518ad1ad..57b11cb78270a8094f772da497ad3264a0a67db1 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java @@ -22138,7 +21422,7 @@ index 963a596154091b79ca139af6274aa323518ad1ad..57b11cb78270a8094f772da497ad3264 } diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java -index aac5572c1d40a10cd1d17f89c9eb836718837577..e76d1d787a5ad73b44dd5cc5671c0d392be21bb9 100644 +index beb70310f2e9657fee89cb4b6a9885712b0116e6..4d176c6ef17012cbcbf0c053a148291d01893c3c 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java @@ -194,12 +194,11 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen @@ -22254,23 +21538,23 @@ index aac5572c1d40a10cd1d17f89c9eb836718837577..e76d1d787a5ad73b44dd5cc5671c0d39 flag = true; } else if (HopperBlockEntity.canMergeItems(itemstack1, stack)) { diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java -index 902f2b39104bf059849228829bfe93b6dbc757d4..9a6c51652bd2dbb3d474809372df03038e34ce75 100644 +index dfcc141dec0ff6e7704af8852a65783558398a67..c1109fce0bdbeb5ff935abdf5c7c9a7f618fdc31 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java -@@ -86,9 +86,9 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi +@@ -35,9 +35,9 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi } public static void serverTick(Level world, BlockPos pos, BlockState state, SculkCatalystBlockEntity blockEntity) { - org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = blockEntity.getBlockPos(); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. + org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverrideRT.set(blockEntity.getBlockPos()); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. // Folia - region threading - blockEntity.sculkSpreader.updateCursors(world, pos, world.getRandom(), true); + blockEntity.catalystListener.getSculkSpreader().updateCursors(world, pos, world.getRandom(), true); - org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = null; // CraftBukkit + org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverrideRT.set(null); // CraftBukkit // Folia - region threading } @Override diff --git a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java -index c2bc747e9b9dd02971c13cb88e7aebeca8c0f2aa..b60d74a5e238d800154d53b46ce1ebc9f4f34c0b 100644 +index d9baa85962236c42219cf09d4f3129be93ff069c..cc01030a2aa6aade37ed7d9e45004e33ae9a0f81 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java @@ -51,9 +51,12 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity { @@ -22630,7 +21914,7 @@ index a743f36f2682a6b72ffa6644782fc081d1479eb7..f5263a71d97f404c6f6dbd3354a5ed2e // CraftBukkit end } diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java -index 221c5d080d55326e458c1182823d6b49224ef498..0a1c876d68e834136f71f226c421699eb523de5a 100644 +index 30fafbb26a347b73c72b9f5c30da3b01e42b851c..e88eba42c8736920afcef92e351eade4c90d94b4 100644 --- a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java @@ -144,8 +144,8 @@ public class PistonMovingBlockEntity extends BlockEntity { @@ -22674,18 +21958,9 @@ index 204f008dc36212e696fba781fede88044b2f735a..1bc2b24deba7a534478184a6a3f3d411 // Paper end diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -index 42ecfc1237bfc724dc9134a6a8bae3670251449e..2fbfdf1fdba12417e2dbca041b9c7e29af27d02d 100644 +index 8bab3fcfc6aa6c0b37621474a69f15e94bda2113..dfae4b5bb0b7e0439c916bc470b32622e96187ea 100644 --- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -306,7 +306,7 @@ public abstract class ChunkGenerator { - return Pair.of(placement.getLocatePos(pos), holder); - } - -- ChunkAccess ichunkaccess = world.getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_STARTS); -+ ChunkAccess ichunkaccess = world.syncLoadNonFull(pos.x, pos.z, ChunkStatus.STRUCTURE_STARTS); // Folia - region threading - - structurestart = structureAccessor.getStartForStructure(SectionPos.bottomOf(ichunkaccess), (Structure) holder.value(), ichunkaccess); - } while (structurestart == null); @@ -317,7 +317,7 @@ public abstract class ChunkGenerator { } @@ -22696,10 +21971,10 @@ index 42ecfc1237bfc724dc9134a6a8bae3670251449e..2fbfdf1fdba12417e2dbca041b9c7e29 return true; } else { diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 6143607ad1485c552309ac3df37b3ba9adb7c07c..e78bb4416d9fac6465738071b529564a6d47391b 100644 +index 17398a48ff84ba1b21bd64f7857e3a326fcc54cf..2e89313f845d370d07745936cde7aad3c562d51b 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -61,6 +61,13 @@ public class LevelChunk extends ChunkAccess { +@@ -59,6 +59,13 @@ public class LevelChunk extends ChunkAccess { @Override public void tick() {} @@ -22713,7 +21988,7 @@ index 6143607ad1485c552309ac3df37b3ba9adb7c07c..e78bb4416d9fac6465738071b529564a @Override public boolean isRemoved() { return true; -@@ -226,51 +233,15 @@ public class LevelChunk extends ChunkAccess { +@@ -222,51 +229,15 @@ public class LevelChunk extends ChunkAccess { } // Paper end // Paper start - optimise checkDespawn @@ -22770,7 +22045,7 @@ index 6143607ad1485c552309ac3df37b3ba9adb7c07c..e78bb4416d9fac6465738071b529564a double distance = player.distanceToSqr(sourceX, sourceY, sourceZ); if (distance < closestDistance && predicate.test(player)) { -@@ -278,31 +249,17 @@ public class LevelChunk extends ChunkAccess { +@@ -274,31 +245,17 @@ public class LevelChunk extends ChunkAccess { closestDistance = distance; } } @@ -22807,7 +22082,7 @@ index 6143607ad1485c552309ac3df37b3ba9adb7c07c..e78bb4416d9fac6465738071b529564a if (range >= 0.0) { double distanceSquared = player.distanceToSqr(sourceX, sourceY, sourceZ); -@@ -315,6 +272,7 @@ public class LevelChunk extends ChunkAccess { +@@ -311,6 +268,7 @@ public class LevelChunk extends ChunkAccess { ret.add(player); } } @@ -22815,7 +22090,7 @@ index 6143607ad1485c552309ac3df37b3ba9adb7c07c..e78bb4416d9fac6465738071b529564a } // Paper end - optimise checkDespawn -@@ -550,7 +508,7 @@ public class LevelChunk extends ChunkAccess { +@@ -557,7 +515,7 @@ public class LevelChunk extends ChunkAccess { return null; } else { // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled. @@ -22824,7 +22099,7 @@ index 6143607ad1485c552309ac3df37b3ba9adb7c07c..e78bb4416d9fac6465738071b529564a iblockdata.onPlace(this.level, blockposition, iblockdata1, flag); } -@@ -597,7 +555,7 @@ public class LevelChunk extends ChunkAccess { +@@ -604,7 +562,7 @@ public class LevelChunk extends ChunkAccess { @Nullable public BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType) { // CraftBukkit start @@ -22833,7 +22108,7 @@ index 6143607ad1485c552309ac3df37b3ba9adb7c07c..e78bb4416d9fac6465738071b529564a if (tileentity == null) { tileentity = (BlockEntity) this.blockEntities.get(pos); } -@@ -883,13 +841,13 @@ public class LevelChunk extends ChunkAccess { +@@ -891,13 +849,13 @@ public class LevelChunk extends ChunkAccess { org.bukkit.World world = this.level.getWorld(); if (world != null) { @@ -22849,7 +22124,7 @@ index 6143607ad1485c552309ac3df37b3ba9adb7c07c..e78bb4416d9fac6465738071b529564a } } server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk)); -@@ -939,7 +897,7 @@ public class LevelChunk extends ChunkAccess { +@@ -947,7 +905,7 @@ public class LevelChunk extends ChunkAccess { @Override public boolean isUnsaved() { // Paper start - add dirty system to tick lists @@ -22858,7 +22133,7 @@ index 6143607ad1485c552309ac3df37b3ba9adb7c07c..e78bb4416d9fac6465738071b529564a if (this.blockTicks.isDirty(gameTime) || this.fluidTicks.isDirty(gameTime)) { return true; } -@@ -1220,6 +1178,13 @@ public class LevelChunk extends ChunkAccess { +@@ -1213,6 +1171,13 @@ public class LevelChunk extends ChunkAccess { this.ticker = wrapped; } @@ -22872,7 +22147,7 @@ index 6143607ad1485c552309ac3df37b3ba9adb7c07c..e78bb4416d9fac6465738071b529564a @Override public void tick() { this.ticker.tick(); -@@ -1256,6 +1221,13 @@ public class LevelChunk extends ChunkAccess { +@@ -1249,6 +1214,13 @@ public class LevelChunk extends ChunkAccess { this.ticker = blockentityticker; } @@ -22887,10 +22162,10 @@ index 6143607ad1485c552309ac3df37b3ba9adb7c07c..e78bb4416d9fac6465738071b529564a public void tick() { if (!this.blockEntity.isRemoved() && this.blockEntity.hasLevel()) { diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 9d6f4749ed72fe319754ccea28f20fa97286527d..3c9ae42290c2e0cb70bb08e97f3ea2c3fb594c7d 100644 +index ebcc7ece0654abd368fc759029f87a7cec9ed036..0c11888bbce6c444189ba268579a5db2a1b5a8af 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -686,7 +686,7 @@ public class ChunkSerializer { +@@ -635,7 +635,7 @@ public class ChunkSerializer { } private static void saveTicks(ServerLevel world, CompoundTag nbt, ChunkAccess.TicksToSave tickSchedulers) { @@ -22900,18 +22175,27 @@ index 9d6f4749ed72fe319754ccea28f20fa97286527d..3c9ae42290c2e0cb70bb08e97f3ea2c3 nbt.put("block_ticks", tickSchedulers.blocks().save(i, (block) -> { return BuiltInRegistries.BLOCK.getKey(block).toString(); diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -index e9eb32469a5c03f7a3677ef50fd4541c1ed662ad..88b1a06fc44980d05dabd3943caa24091ff4de98 100644 +index e2cd77d9de9709aa50f4b6febabe21bfcf94dc5d..33bc2db3e24fc850dff69225261caeb7d6449c68 100644 --- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -@@ -168,6 +168,7 @@ public class EndDragonFight { +@@ -72,7 +72,7 @@ public class EndDragonFight { + private static final Component DEFAULT_BOSS_EVENT_NAME = Component.translatable("entity.minecraft.ender_dragon"); // Paper + public final ServerBossEvent dragonEvent = (ServerBossEvent)(new ServerBossEvent(DEFAULT_BOSS_EVENT_NAME, BossEvent.BossBarColor.PINK, BossEvent.BossBarOverlay.PROGRESS)).setPlayBossMusic(true).setCreateWorldFog(true); // Paper + public final ServerLevel level; +- private final BlockPos origin; ++ public final BlockPos origin; // Folia - region threading + private final ObjectArrayList gateways = new ObjectArrayList<>(); + private final BlockPattern exitPortalPattern; + private int ticksSinceDragonSeen; +@@ -143,6 +143,7 @@ public class EndDragonFight { if (!this.dragonEvent.getPlayers().isEmpty()) { this.level.getChunkSource().addRegionTicket(TicketType.DRAGON, new ChunkPos(0, 0), 9, Unit.INSTANCE); boolean bl = this.isArenaLoaded(); -+ if (!bl) { return; }// Folia - region threading - don't tick if we don't own the entire region ++ if (!bl) { return; } // Folia - region threading - don't tick if we don't own the entire region if (this.needsStateScanning && bl) { this.scanState(); this.needsStateScanning = false; -@@ -214,6 +215,12 @@ public class EndDragonFight { +@@ -189,6 +190,12 @@ public class EndDragonFight { } List list = this.level.getDragons(); @@ -22924,32 +22208,32 @@ index e9eb32469a5c03f7a3677ef50fd4541c1ed662ad..88b1a06fc44980d05dabd3943caa2409 if (list.isEmpty()) { this.dragonKilled = true; } else { -@@ -324,8 +331,8 @@ public class EndDragonFight { - private boolean isArenaLoaded() { - for(int i = -8; i <= 8; ++i) { - for(int j = 8; j <= 8; ++j) { -- ChunkAccess chunkAccess = this.level.getChunk(i, j, ChunkStatus.FULL, false); -- if (!(chunkAccess instanceof LevelChunk)) { -+ ChunkAccess chunkAccess = this.level.getChunkIfLoaded(i, j); // Folia - region threading -+ if (!(chunkAccess instanceof LevelChunk) || !io.papermc.paper.util.TickThread.isTickThreadFor(this.level, i, j, this.level.regioniser.regionSectionChunkSize)) { // Folia - region threading - return false; - } +@@ -307,8 +314,8 @@ public class EndDragonFight { -@@ -465,7 +472,7 @@ public class EndDragonFight { + for(int i = -8 + chunkPos.x; i <= 8 + chunkPos.x; ++i) { + for(int j = 8 + chunkPos.z; j <= 8 + chunkPos.z; ++j) { +- ChunkAccess chunkAccess = this.level.getChunk(i, j, ChunkStatus.FULL, false); +- if (!(chunkAccess instanceof LevelChunk)) { ++ ChunkAccess chunkAccess = this.level.getChunkIfLoaded(i, j); // Folia - region threading ++ if (!(chunkAccess instanceof LevelChunk) || !io.papermc.paper.util.TickThread.isTickThreadFor(this.level, i, j, this.level.regioniser.regionSectionChunkSize)) { // Folia - region threading + return false; + } + +@@ -458,7 +465,7 @@ public class EndDragonFight { } public void onCrystalDestroyed(EndCrystal enderCrystal, DamageSource source) { - if (this.respawnStage != null && this.respawnCrystals.contains(enderCrystal)) { -+ if (io.papermc.paper.util.TickThread.isTickThreadFor(this.level, 0, 0) && this.respawnStage != null && this.respawnCrystals.contains(enderCrystal)) { ++ if (io.papermc.paper.util.TickThread.isTickThreadFor(this.level, this.origin) && this.respawnStage != null && this.respawnCrystals.contains(enderCrystal)) { LOGGER.debug("Aborting respawn sequence"); this.respawnStage = null; this.respawnTime = 0; -@@ -486,7 +493,7 @@ public class EndDragonFight { +@@ -479,7 +486,7 @@ public class EndDragonFight { } public void tryRespawn() { - if (this.dragonKilled && this.respawnStage == null) { -+ if (this.dragonKilled && this.respawnStage == null && io.papermc.paper.util.TickThread.isTickThreadFor(this.level, 0, 0)) { // Folia - region threading ++ if (this.dragonKilled && this.respawnStage == null && io.papermc.paper.util.TickThread.isTickThreadFor(this.level, this.origin)) { // Folia - region threading BlockPos blockPos = this.portalLocation; if (blockPos == null) { LOGGER.debug("Tried to respawn, but need to find the portal first."); @@ -23006,10 +22290,10 @@ index a908652f1ebb426d265ef614746f70cd1e538268..e615b79f68a0467aa8cfa1c61b06ae04 if (days >= world.paperConfig().entities.behavior.pillagerPatrols.start.day && world.isDay()) { diff --git a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -index e8ae4449696d73c8c9b8b27d4d2e20db933a72cc..99959b50d06d550ce56bb26e35ec38ec741479da 100644 +index bc7648dbc132551dc6591ab49a1919a623c30f60..acdbf170d49f15bf0956ee52db3916cb51797656 100644 --- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java +++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -@@ -24,7 +24,7 @@ import net.minecraft.world.level.material.FluidState; +@@ -22,7 +22,7 @@ import net.minecraft.world.level.material.FluidState; public class PhantomSpawner implements CustomSpawner { @@ -23018,7 +22302,7 @@ index e8ae4449696d73c8c9b8b27d4d2e20db933a72cc..99959b50d06d550ce56bb26e35ec38ec public PhantomSpawner() {} -@@ -42,20 +42,22 @@ public class PhantomSpawner implements CustomSpawner { +@@ -40,20 +40,22 @@ public class PhantomSpawner implements CustomSpawner { // Paper end RandomSource randomsource = world.random; @@ -23044,170 +22328,7 @@ index e8ae4449696d73c8c9b8b27d4d2e20db933a72cc..99959b50d06d550ce56bb26e35ec38ec + Iterator iterator = world.getLocalPlayers().iterator(); // Folia - region threading while (iterator.hasNext()) { - Player entityhuman = (Player) iterator.next(); -diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -index 1ca00340aaa201dd34e5c350d23ef53e126a0ca6..cf664ed17c6c138aed2a8680b51480ded47a2e50 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java -@@ -50,8 +50,101 @@ public class StructureCheck { - private final BiomeSource biomeSource; - private final long seed; - private final DataFixer fixerUpper; -- private final Long2ObjectMap> loadedChunks = new Long2ObjectOpenHashMap<>(); -- private final Map featureChecks = new HashMap<>(); -+ // Foila start - synchronise this class -+ // additionally, make sure to purge entries from the maps so it does not leak memory -+ private static final int CHUNK_TOTAL_LIMIT = 50 * (2 * 100 + 1) * (2 * 100 + 1); // cache 50 structure lookups -+ private static final int PER_FEATURE_CHECK_LIMIT = 50 * (2 * 100 + 1) * (2 * 100 + 1); // cache 50 structure lookups -+ -+ private final SynchronisedLong2ObjectMap> loadedChunksSafe = new SynchronisedLong2ObjectMap<>(CHUNK_TOTAL_LIMIT); -+ private final java.util.concurrent.ConcurrentHashMap featureChecksSafe = new java.util.concurrent.ConcurrentHashMap<>(); -+ -+ private static final class SynchronisedLong2ObjectMap { -+ private final it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap map = new it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap<>(); -+ private final int limit; -+ -+ public SynchronisedLong2ObjectMap(final int limit) { -+ this.limit = limit; -+ } -+ -+ // must hold lock on map -+ private void purgeEntries() { -+ while (this.map.size() > this.limit) { -+ this.map.removeLast(); -+ } -+ } -+ -+ public V get(final long key) { -+ synchronized (this.map) { -+ return this.map.getAndMoveToFirst(key); -+ } -+ } -+ -+ public V put(final long key, final V value) { -+ synchronized (this.map) { -+ final V ret = this.map.putAndMoveToFirst(key, value); -+ this.purgeEntries(); -+ return ret; -+ } -+ } -+ -+ public V compute(final long key, final java.util.function.BiFunction remappingFunction) { -+ synchronized (this.map) { -+ // first, compute the value - if one is added, it will be at the last entry -+ this.map.compute(key, remappingFunction); -+ // move the entry to first, just in case it was added at last -+ final V ret = this.map.getAndMoveToFirst(key); -+ // now purge the last entries -+ this.purgeEntries(); -+ -+ return ret; -+ } -+ } -+ } -+ -+ private static final class SynchronisedLong2BooleanMap { -+ private final it.unimi.dsi.fastutil.longs.Long2BooleanLinkedOpenHashMap map = new it.unimi.dsi.fastutil.longs.Long2BooleanLinkedOpenHashMap(); -+ private final int limit; -+ -+ public SynchronisedLong2BooleanMap(final int limit) { -+ this.limit = limit; -+ } -+ -+ // must hold lock on map -+ private void purgeEntries() { -+ while (this.map.size() > this.limit) { -+ this.map.removeLastBoolean(); -+ } -+ } -+ -+ public boolean remove(final long key) { -+ synchronized (this.map) { -+ return this.map.remove(key); -+ } -+ } -+ -+ // note: -+ public boolean getOrCompute(final long key, final it.unimi.dsi.fastutil.longs.Long2BooleanFunction ifAbsent) { -+ synchronized (this.map) { -+ if (this.map.containsKey(key)) { -+ return this.map.getAndMoveToFirst(key); -+ } -+ } -+ -+ final boolean put = ifAbsent.get(key); -+ -+ synchronized (this.map) { -+ if (this.map.containsKey(key)) { -+ return this.map.getAndMoveToFirst(key); -+ } -+ this.map.putAndMoveToFirst(key, put); -+ -+ this.purgeEntries(); -+ -+ return put; -+ } -+ } -+ } -+ // Foila end - synchronise this class - - public StructureCheck(ChunkScanAccess chunkIoWorker, RegistryAccess registryManager, StructureTemplateManager structureTemplateManager, ResourceKey worldKey, ChunkGenerator chunkGenerator, RandomState noiseConfig, LevelHeightAccessor world, BiomeSource biomeSource, long seed, DataFixer dataFixer) { // Paper - fix missing CB diff - this.storageAccess = chunkIoWorker; -@@ -70,7 +163,7 @@ public class StructureCheck { - - public StructureCheckResult checkStart(ChunkPos pos, Structure type, boolean skipReferencedStructures) { - long l = pos.toLong(); -- Object2IntMap object2IntMap = this.loadedChunks.get(l); -+ Object2IntMap object2IntMap = this.loadedChunksSafe.get(l); // Folia - region threading - if (object2IntMap != null) { - return this.checkStructureInfo(object2IntMap, type, skipReferencedStructures); - } else { -@@ -78,9 +171,9 @@ public class StructureCheck { - if (structureCheckResult != null) { - return structureCheckResult; - } else { -- boolean bl = this.featureChecks.computeIfAbsent(type, (structure2) -> { -- return new Long2BooleanOpenHashMap(); -- }).computeIfAbsent(l, (chunkPos) -> { -+ boolean bl = this.featureChecksSafe.computeIfAbsent(type, (structure2) -> { // Folia - region threading -+ return new SynchronisedLong2BooleanMap(PER_FEATURE_CHECK_LIMIT); // Folia - region threading -+ }).getOrCompute(l, (chunkPos) -> { // Folia - region threading - return this.canCreateStructure(pos, type); - }); - return !bl ? StructureCheckResult.START_NOT_PRESENT : StructureCheckResult.CHUNK_LOAD_NEEDED; -@@ -193,17 +286,26 @@ public class StructureCheck { - } - - private void storeFullResults(long pos, Object2IntMap referencesByStructure) { -- this.loadedChunks.put(pos, deduplicateEmptyMap(referencesByStructure)); -- this.featureChecks.values().forEach((generationPossibilityByChunkPos) -> { -- generationPossibilityByChunkPos.remove(pos); -- }); -+ // Folia start - region threading - make access mt-safe -+ this.loadedChunksSafe.put(pos, deduplicateEmptyMap(referencesByStructure)); -+ // once we insert into loadedChunks, we don't really need to be very careful about removing everything -+ // from this map, as everything that checks this map uses loadedChunks first -+ // so, one way or another it's a race condition that doesn't matter -+ for (SynchronisedLong2BooleanMap value : this.featureChecksSafe.values()) { -+ value.remove(pos); -+ } -+ // Folia end - region threading - make access mt-safe - } - - public void incrementReference(ChunkPos pos, Structure structure) { -- this.loadedChunks.compute(pos.toLong(), (posx, referencesByStructure) -> { -- if (referencesByStructure == null || referencesByStructure.isEmpty()) { -+ this.loadedChunksSafe.compute(pos.toLong(), (posx, referencesByStructure) -> { // Folia start - region threading -+ // make this COW so that we do not mutate state that may be currently in use -+ if (referencesByStructure == null) { - referencesByStructure = new Object2IntOpenHashMap<>(); -+ } else { -+ referencesByStructure = referencesByStructure instanceof Object2IntOpenHashMap fastClone ? fastClone.clone() : new Object2IntOpenHashMap<>(referencesByStructure); - } -+ // Folia end - region threading - - referencesByStructure.computeInt(structure, (feature, references) -> { - return references == null ? 1 : references + 1; + ServerPlayer entityplayer = (ServerPlayer) iterator.next(); diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureStart.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureStart.java index 6570e0b61d7602c57c61398ddce50418d0719ff2..bcee13beed247f7830ee85d099c367dbfd1ea51d 100644 --- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureStart.java @@ -23272,7 +22393,7 @@ index 6570e0b61d7602c57c61398ddce50418d0719ff2..bcee13beed247f7830ee85d099c367db protected int getMaxReferences() { diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -index 92d13c9f1ec1e5ff72c1d68f924a8d1c86c91565..3f224315f1e0a8f69e9d84d27fd7dbe45ea75667 100644 +index 9b38f4c81ca9ef0f91f9d59fc2be4eecc1afc165..3e883ecd5debe122ed968f94f831d6f5e0ec79d3 100644 --- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java +++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java @@ -89,10 +89,10 @@ public class PortalForcer { @@ -23289,7 +22410,7 @@ index 92d13c9f1ec1e5ff72c1d68f924a8d1c86c91565..3f224315f1e0a8f69e9d84d27fd7dbe4 }); } diff --git a/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java b/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java -index b1c594dc6a6b8a6c737b99272acab9e7dbd0ed63..7c1768452fa0f7278ccc84470ef0965a3f96b0df 100644 +index ec81be70cd6f92bbf9011395cb361f0ce54c5ad0..d50c7dd008af14fce9073666e0fd1b609f89559c 100644 --- a/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java +++ b/src/main/java/net/minecraft/world/level/redstone/CollectingNeighborUpdater.java @@ -46,6 +46,7 @@ public class CollectingNeighborUpdater implements NeighborUpdater { @@ -23357,7 +22478,7 @@ index 9b2948b5150c8f039ca667a50765109721b93947..1b76e4edce628f2b25815e28cd4cb750 } } diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -index 25a64250ce57fe4cd90f8b95d1e003d961662152..6263b8b680b076a79c0bf278c6eb53843984cc76 100644 +index 3b12030b49b1c539684d75ca3896eb498400ef99..2b862066388cfede202a0c709f93ae89e2d17b30 100644 --- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java @@ -185,7 +185,7 @@ public class MapItemSavedData extends SavedData { @@ -23393,7 +22514,7 @@ index 25a64250ce57fe4cd90f8b95d1e003d961662152..6263b8b680b076a79c0bf278c6eb5384 if (!this.carriedByPlayers.containsKey(player)) { MapItemSavedData.HoldingPlayer worldmap_worldmaphumantracker = new MapItemSavedData.HoldingPlayer(player); -@@ -368,7 +369,7 @@ public class MapItemSavedData extends SavedData { +@@ -366,7 +367,7 @@ public class MapItemSavedData extends SavedData { rotation += rotation < 0.0D ? -8.0D : 8.0D; b2 = (byte) ((int) (rotation * 16.0D / 360.0D)); if (this.dimension == Level.NETHER && world != null) { @@ -23402,7 +22523,7 @@ index 25a64250ce57fe4cd90f8b95d1e003d961662152..6263b8b680b076a79c0bf278c6eb5384 b2 = (byte) (j * j * 34187121 + j * 121 >> 15 & 15); } -@@ -427,14 +428,14 @@ public class MapItemSavedData extends SavedData { +@@ -425,14 +426,14 @@ public class MapItemSavedData extends SavedData { } @Nullable @@ -23420,7 +22541,7 @@ index 25a64250ce57fe4cd90f8b95d1e003d961662152..6263b8b680b076a79c0bf278c6eb5384 Iterator iterator = this.carriedBy.iterator(); while (iterator.hasNext()) { -@@ -442,15 +443,16 @@ public class MapItemSavedData extends SavedData { +@@ -440,15 +441,16 @@ public class MapItemSavedData extends SavedData { worldmap_worldmaphumantracker.markColorsDirty(x, z); } @@ -23441,7 +22562,7 @@ index 25a64250ce57fe4cd90f8b95d1e003d961662152..6263b8b680b076a79c0bf278c6eb5384 MapItemSavedData.HoldingPlayer worldmap_worldmaphumantracker = (MapItemSavedData.HoldingPlayer) this.carriedByPlayers.get(player); if (worldmap_worldmaphumantracker == null) { -@@ -462,7 +464,7 @@ public class MapItemSavedData extends SavedData { +@@ -460,7 +462,7 @@ public class MapItemSavedData extends SavedData { return worldmap_worldmaphumantracker; } @@ -23450,7 +22571,7 @@ index 25a64250ce57fe4cd90f8b95d1e003d961662152..6263b8b680b076a79c0bf278c6eb5384 double d0 = (double) pos.getX() + 0.5D; double d1 = (double) pos.getZ() + 0.5D; int i = 1 << this.scale; -@@ -471,7 +473,7 @@ public class MapItemSavedData extends SavedData { +@@ -469,7 +471,7 @@ public class MapItemSavedData extends SavedData { boolean flag = true; if (d2 >= -63.0D && d3 >= -63.0D && d2 <= 63.0D && d3 <= 63.0D) { @@ -23459,7 +22580,7 @@ index 25a64250ce57fe4cd90f8b95d1e003d961662152..6263b8b680b076a79c0bf278c6eb5384 if (mapiconbanner == null) { return false; -@@ -492,7 +494,7 @@ public class MapItemSavedData extends SavedData { +@@ -490,7 +492,7 @@ public class MapItemSavedData extends SavedData { return false; } @@ -23468,7 +22589,7 @@ index 25a64250ce57fe4cd90f8b95d1e003d961662152..6263b8b680b076a79c0bf278c6eb5384 Iterator iterator = this.bannerMarkers.values().iterator(); while (iterator.hasNext()) { -@@ -514,12 +516,12 @@ public class MapItemSavedData extends SavedData { +@@ -512,12 +514,12 @@ public class MapItemSavedData extends SavedData { return this.bannerMarkers.values(); } @@ -23483,7 +22604,7 @@ index 25a64250ce57fe4cd90f8b95d1e003d961662152..6263b8b680b076a79c0bf278c6eb5384 byte b1 = this.colors[x + z * 128]; if (b1 != color) { -@@ -530,12 +532,12 @@ public class MapItemSavedData extends SavedData { +@@ -528,12 +530,12 @@ public class MapItemSavedData extends SavedData { } } @@ -23498,7 +22619,7 @@ index 25a64250ce57fe4cd90f8b95d1e003d961662152..6263b8b680b076a79c0bf278c6eb5384 Iterator iterator = this.decorations.values().iterator(); MapDecoration mapicon; -@@ -570,7 +572,7 @@ public class MapItemSavedData extends SavedData { +@@ -568,7 +570,7 @@ public class MapItemSavedData extends SavedData { return this.decorations.values(); } @@ -23507,7 +22628,7 @@ index 25a64250ce57fe4cd90f8b95d1e003d961662152..6263b8b680b076a79c0bf278c6eb5384 return this.trackedDecorationCount >= iconCount; } -@@ -696,11 +698,13 @@ public class MapItemSavedData extends SavedData { +@@ -694,11 +696,13 @@ public class MapItemSavedData extends SavedData { } public void applyToMap(MapItemSavedData mapState) { @@ -23696,7 +22817,7 @@ index 1d7c663fa0e550bd0cfb9a4b83ccd7e2968666f0..f3df9c9b6cff85565514f990597f3fe5 LevelChunkTicks levelChunkTicks = this.allContainers.get(l); if (levelChunkTicks == null) { diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 91c301275fa7b6a2fc16e3bdbf61b6a46eed6ef4..c43248f5a8e4fe514cfcc1dcf3003baedd950904 100644 +index ce4238d0cb82cc16fc2e5f534ab4a7eef50b5437..aa877017799c573a114832fd1809bc80e322ed2c 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -310,6 +310,82 @@ public final class CraftServer implements Server { @@ -23837,7 +22958,7 @@ index 91c301275fa7b6a2fc16e3bdbf61b6a46eed6ef4..c43248f5a8e4fe514cfcc1dcf3003bae // Paper Start if (!org.spigotmc.AsyncCatcher.shuttingDown && !Bukkit.isPrimaryThread()) { final CommandSender fSender = sender; -@@ -2951,7 +3062,7 @@ public final class CraftServer implements Server { +@@ -2947,7 +3058,7 @@ public final class CraftServer implements Server { @Override public int getCurrentTick() { @@ -23847,7 +22968,7 @@ index 91c301275fa7b6a2fc16e3bdbf61b6a46eed6ef4..c43248f5a8e4fe514cfcc1dcf3003bae @Override diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f3a479b34 100644 +index 439857a814212b36e475461a01b320731a10b86d..be71724effeae857123aac6e34393fab048bf93d 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -184,7 +184,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @@ -23916,7 +23037,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f warnUnsafeChunk("regenerating a faraway chunk", x, z); // Paper // Paper start - implement regenerateChunk method final ServerLevel serverLevel = this.world; -@@ -504,6 +505,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -503,6 +504,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean refreshChunk(int x, int z) { @@ -23924,7 +23045,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z)); if (playerChunk == null) return false; -@@ -539,7 +541,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -533,7 +535,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean loadChunk(int x, int z, boolean generate) { @@ -23933,7 +23054,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f warnUnsafeChunk("loading a faraway chunk", x, z); // Paper // Paper start - Optimize this method ChunkPos chunkPos = new ChunkPos(x, z); -@@ -611,7 +613,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -605,7 +607,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { DistanceManager chunkDistanceManager = this.world.getChunkSource().chunkMap.distanceManager; if (chunkDistanceManager.addRegionTicketAtDistance(TicketType.PLUGIN_TICKET, new ChunkPos(x, z), 2, plugin)) { // keep in-line with force loading, add at level 31 @@ -23942,7 +23063,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f return true; } -@@ -796,13 +798,15 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -790,13 +792,15 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { @@ -23963,7 +23084,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f BlockPos position = ((CraftBlockState) blockstate).getPosition(); net.minecraft.world.level.block.state.BlockState oldBlock = this.world.getBlockState(position); int flag = ((CraftBlockState) blockstate).getFlag(); -@@ -810,10 +814,10 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -804,10 +808,10 @@ public class CraftWorld extends CraftRegionAccessor implements World { net.minecraft.world.level.block.state.BlockState newBlock = this.world.getBlockState(position); this.world.notifyAndUpdatePhysics(position, null, oldBlock, newBlock, newBlock, flag, 512); } @@ -23976,7 +23097,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f return false; } } -@@ -847,6 +851,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -841,6 +845,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void setTime(long time) { @@ -23984,7 +23105,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f long margin = (time - this.getFullTime()) % 24000; if (margin < 0) margin += 24000; this.setFullTime(this.getFullTime() + margin); -@@ -859,6 +864,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -853,6 +858,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void setFullTime(long time) { @@ -23992,7 +23113,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f // Notify anyone who's listening TimeSkipEvent event = new TimeSkipEvent(this, TimeSkipEvent.SkipReason.CUSTOM, time - this.world.getDayTime()); this.server.getPluginManager().callEvent(event); -@@ -886,7 +892,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -880,7 +886,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public long getGameTime() { @@ -24001,7 +23122,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f } @Override -@@ -906,11 +912,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -900,11 +906,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean createExplosion(double x, double y, double z, float power, boolean setFire, boolean breakBlocks, Entity source) { @@ -24015,7 +23136,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f return !world.explode(source != null ? ((org.bukkit.craftbukkit.entity.CraftEntity) source).getHandle() : null, loc.getX(), loc.getY(), loc.getZ(), power, setFire, breakBlocks ? net.minecraft.world.level.Level.ExplosionInteraction.MOB : net.minecraft.world.level.Level.ExplosionInteraction.NONE).wasCanceled; } // Paper end -@@ -980,6 +988,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -974,6 +982,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public int getHighestBlockYAt(int x, int z, org.bukkit.HeightMap heightMap) { @@ -24023,7 +23144,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f warnUnsafeChunk("getting a faraway chunk", x >> 4, z >> 4); // Paper // Transient load for this tick return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z); -@@ -1010,6 +1019,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1004,6 +1013,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void setBiome(int x, int y, int z, Holder bb) { BlockPos pos = new BlockPos(x, 0, z); @@ -24031,7 +23152,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f if (this.world.hasChunkAt(pos)) { net.minecraft.world.level.chunk.LevelChunk chunk = this.world.getChunkAt(pos); -@@ -1285,6 +1295,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1279,6 +1289,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void setStorm(boolean hasStorm) { @@ -24039,7 +23160,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f world.serverLevelData.setRaining(hasStorm, org.bukkit.event.weather.WeatherChangeEvent.Cause.PLUGIN); // Paper this.setWeatherDuration(0); // Reset weather duration (legacy behaviour) this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) -@@ -1297,6 +1308,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1291,6 +1302,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void setWeatherDuration(int duration) { @@ -24047,7 +23168,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f world.serverLevelData.setRainTime(duration); } -@@ -1307,6 +1319,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1301,6 +1313,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void setThundering(boolean thundering) { @@ -24055,7 +23176,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f world.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.PLUGIN); // Paper this.setThunderDuration(0); // Reset weather duration (legacy behaviour) this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands) -@@ -1319,6 +1332,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1313,6 +1326,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void setThunderDuration(int duration) { @@ -24063,7 +23184,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f world.serverLevelData.setThunderTime(duration); } -@@ -1329,6 +1343,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1323,6 +1337,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void setClearWeatherDuration(int duration) { @@ -24071,7 +23192,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f world.serverLevelData.setClearWeatherTime(duration); } -@@ -1522,6 +1537,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1516,6 +1531,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void setKeepSpawnInMemory(boolean keepLoaded) { @@ -24079,7 +23200,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f // Paper start - Configurable spawn radius if (keepLoaded == world.keepSpawnInMemory) { // do nothing, nothing has changed -@@ -1600,6 +1616,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1594,6 +1610,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void setHardcore(boolean hardcore) { @@ -24087,7 +23208,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f world.serverLevelData.settings.hardcore = hardcore; } -@@ -1612,6 +1629,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1606,6 +1623,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override @Deprecated public void setTicksPerAnimalSpawns(int ticksPerAnimalSpawns) { @@ -24095,7 +23216,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f this.setTicksPerSpawns(SpawnCategory.ANIMAL, ticksPerAnimalSpawns); } -@@ -1624,6 +1642,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1618,6 +1636,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override @Deprecated public void setTicksPerMonsterSpawns(int ticksPerMonsterSpawns) { @@ -24103,7 +23224,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f this.setTicksPerSpawns(SpawnCategory.MONSTER, ticksPerMonsterSpawns); } -@@ -1636,6 +1655,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1630,6 +1649,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override @Deprecated public void setTicksPerWaterSpawns(int ticksPerWaterSpawns) { @@ -24111,7 +23232,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f this.setTicksPerSpawns(SpawnCategory.WATER_ANIMAL, ticksPerWaterSpawns); } -@@ -1648,6 +1668,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1642,6 +1662,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override @Deprecated public void setTicksPerWaterAmbientSpawns(int ticksPerWaterAmbientSpawns) { @@ -24119,7 +23240,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f this.setTicksPerSpawns(SpawnCategory.WATER_AMBIENT, ticksPerWaterAmbientSpawns); } -@@ -1660,6 +1681,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1654,6 +1675,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override @Deprecated public void setTicksPerWaterUndergroundCreatureSpawns(int ticksPerWaterUndergroundCreatureSpawns) { @@ -24127,7 +23248,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f this.setTicksPerSpawns(SpawnCategory.WATER_UNDERGROUND_CREATURE, ticksPerWaterUndergroundCreatureSpawns); } -@@ -1672,11 +1694,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1666,11 +1688,13 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override @Deprecated public void setTicksPerAmbientSpawns(int ticksPerAmbientSpawns) { @@ -24141,7 +23262,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f Validate.notNull(spawnCategory, "SpawnCategory cannot be null"); Validate.isTrue(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory." + spawnCategory + " are not supported."); -@@ -1693,21 +1717,25 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1687,21 +1711,25 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { @@ -24167,7 +23288,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f this.server.getWorldMetadata().removeMetadata(this, metadataKey, owningPlugin); } -@@ -1720,6 +1748,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1714,6 +1742,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override @Deprecated public void setMonsterSpawnLimit(int limit) { @@ -24175,7 +23296,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f this.setSpawnLimit(SpawnCategory.MONSTER, limit); } -@@ -1732,6 +1761,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1726,6 +1755,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override @Deprecated public void setAnimalSpawnLimit(int limit) { @@ -24183,7 +23304,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f this.setSpawnLimit(SpawnCategory.ANIMAL, limit); } -@@ -1744,6 +1774,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1738,6 +1768,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override @Deprecated public void setWaterAnimalSpawnLimit(int limit) { @@ -24191,7 +23312,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f this.setSpawnLimit(SpawnCategory.WATER_ANIMAL, limit); } -@@ -1756,6 +1787,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1750,6 +1781,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override @Deprecated public void setWaterAmbientSpawnLimit(int limit) { @@ -24199,7 +23320,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f this.setSpawnLimit(SpawnCategory.WATER_AMBIENT, limit); } -@@ -1768,6 +1800,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1762,6 +1794,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override @Deprecated public void setWaterUndergroundCreatureSpawnLimit(int limit) { @@ -24207,7 +23328,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f this.setSpawnLimit(SpawnCategory.WATER_UNDERGROUND_CREATURE, limit); } -@@ -1780,6 +1813,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1774,6 +1807,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override @Deprecated public void setAmbientSpawnLimit(int limit) { @@ -24215,7 +23336,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f this.setSpawnLimit(SpawnCategory.AMBIENT, limit); } -@@ -1802,6 +1836,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1796,6 +1830,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void setSpawnLimit(SpawnCategory spawnCategory, int limit) { @@ -24223,7 +23344,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f Validate.notNull(spawnCategory, "SpawnCategory cannot be null"); Validate.isTrue(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory." + spawnCategory + " are not supported."); -@@ -1856,7 +1891,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1850,7 +1885,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(BuiltInRegistries.SOUND_EVENT.wrapAsHolder(CraftSound.getSoundEffect(sound)), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, this.getHandle().getRandom().nextLong()); @@ -24232,7 +23353,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f if (entityTracker != null) { entityTracker.broadcastAndSend(packet); } -@@ -1867,7 +1902,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1861,7 +1896,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return; ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(Holder.direct(SoundEvent.createVariableRangeEvent(new ResourceLocation(sound))), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, this.getHandle().getRandom().nextLong()); @@ -24241,7 +23362,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f if (entityTracker != null) { entityTracker.broadcastAndSend(packet); } -@@ -1953,6 +1988,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1947,6 +1982,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean setGameRuleValue(String rule, String value) { @@ -24249,7 +23370,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f // No null values allowed if (rule == null || value == null) return false; -@@ -1994,6 +2030,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -1988,6 +2024,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public boolean setGameRule(GameRule rule, T newValue) { @@ -24257,7 +23378,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f Validate.notNull(rule, "GameRule cannot be null"); Validate.notNull(newValue, "GameRule value cannot be null"); -@@ -2259,6 +2296,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -2253,6 +2290,12 @@ public class CraftWorld extends CraftRegionAccessor implements World { @Override public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) { @@ -24270,7 +23391,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f getHandle().gameEvent(sourceEntity != null ? ((CraftEntity) sourceEntity).getHandle(): null, net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.get(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(gameEvent.getKey())), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position)); } // Paper end -@@ -2391,7 +2434,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -2385,7 +2428,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { // Paper start public java.util.concurrent.CompletableFuture getChunkAtAsync(int x, int z, boolean gen, boolean urgent) { warnUnsafeChunk("getting a faraway chunk async", x, z); // Paper @@ -24279,7 +23400,7 @@ index 4f0ca1621e4f1fc16d4a6601c858d74ed4d5a788..58450e07930084527c4e89ba5d376f6f net.minecraft.world.level.chunk.LevelChunk immediate = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); if (immediate != null) { return java.util.concurrent.CompletableFuture.completedFuture(new CraftChunk(immediate)); -@@ -2408,7 +2451,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { +@@ -2402,7 +2445,7 @@ public class CraftWorld extends CraftRegionAccessor implements World { java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>(); io.papermc.paper.chunk.system.ChunkSystem.scheduleChunkLoad(this.getHandle(), x, z, gen, ChunkStatus.FULL, true, priority, (c) -> { @@ -24301,7 +23422,7 @@ index a3d5e319473e2f6316b3ef8edf719296e02d85a1..55abed5fdf8dc699ade5b25b1f194941 tileentitybeehive.addOccupantWithPresetTicks(entitybee, false, random.nextInt(599)); } diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java -index 3a201bb68ef34d53aee75ca248248c11c4a77c2c..c18803e83da8a428d3adc59f34e17d50a3f6df4a 100644 +index 01b401f7d691ef451266bbc3a94980d613b96f21..d4a1d17d5dde68b9bc2fbae370ef2268f3b604db 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java @@ -80,6 +80,11 @@ public class CraftBlock implements Block { @@ -24555,7 +23676,7 @@ index cd4ad8261e56365850068db1d83d6a8454026737..78f7e72f2912dae503c2dab7d1992b65 List offers = waitable.get(); if (offers == null) { diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 56c75029a94e8812c9e0ce5375aaa7cbcda90b87..fa762f490fea59e2ec21cfb1c050c4adc30d9998 100644 +index ea056babe2f8123f20dc608d8a636da1de634b8c..6a54b582fdab953c452a32e9839e2a916c659c2e 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -203,6 +203,16 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { @@ -24661,7 +23782,7 @@ index 56c75029a94e8812c9e0ce5375aaa7cbcda90b87..fa762f490fea59e2ec21cfb1c050c4ad @Override diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index e88d7a6b4835bdac1a247545b49e4161ade148cb..b0d9ab8088ee8b894fbee4c4b88bf081a419cb1f 100644 +index be933acd3004e7e092be3688d0d9ee97b159ab5a..93e0c4fd626455db1e14785a0b6750ba07fa0fe0 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -573,7 +573,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @@ -24673,7 +23794,7 @@ index e88d7a6b4835bdac1a247545b49e4161ade148cb..b0d9ab8088ee8b894fbee4c4b88bf081 if (this.getHandle().connection == null) return; this.getHandle().connection.disconnect(message == null ? "" : message, org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); // Paper - kick event cause -@@ -1281,6 +1281,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -1287,6 +1287,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public boolean teleport(Location location, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause, io.papermc.paper.entity.TeleportFlag... flags) { @@ -24685,16 +23806,16 @@ index e88d7a6b4835bdac1a247545b49e4161ade148cb..b0d9ab8088ee8b894fbee4c4b88bf081 java.util.Set relativeArguments; java.util.Set allFlags; if (flags.length == 0) { -@@ -1812,7 +1817,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -1818,7 +1823,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { private void unregisterEntity(Entity other) { // Paper end - ChunkMap tracker = ((ServerLevel) this.getHandle().level).getChunkSource().chunkMap; + ChunkMap tracker = ((ServerLevel) this.getHandle().level()).getChunkSource().chunkMap; - ChunkMap.TrackedEntity entry = tracker.entityMap.get(other.getId()); + ChunkMap.TrackedEntity entry = other.tracker; // Folia - region threading if (entry != null) { entry.removePlayer(this.getHandle()); } -@@ -1896,7 +1901,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -1902,7 +1907,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(otherPlayer))); } @@ -24704,7 +23825,7 @@ index e88d7a6b4835bdac1a247545b49e4161ade148cb..b0d9ab8088ee8b894fbee4c4b88bf081 entry.updatePlayer(this.getHandle()); } diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index a153c134cf26e86d49ef419eca35994539af0db3..ca9398250db23bf3198fa96bcac2beb54dda3690 100644 +index 1ced79cf92fe0b01a42f097794dacc3ce74518f3..4e08c4064c9ba2f16b1ae475f19c668b48fb0dc2 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -230,8 +230,8 @@ import org.bukkit.potion.PotionEffect; @@ -24816,7 +23937,7 @@ index cdefb2025eedea7e204d70d568adaf1c1ec4c03c..9136fb30db749737e9f189d0901024fc if (!this.isAsyncScheduler && !task.isSync()) { this.asyncScheduler.handle(task, delay); diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 1179e9fbff93ec8ff82aa3aae477f6bf4ce9b885..3dc2223bcdffb425f207e7418a8bcc55db26842f 100644 +index b059bb46b649cbf82debdb8d58fe4ba77d040276..340b3fb265048eb6fffad5fda33cd0735f7377c1 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java @@ -375,6 +375,12 @@ public final class CraftMagicNumbers implements UnsafeValues { @@ -24833,13 +23954,13 @@ index 1179e9fbff93ec8ff82aa3aae477f6bf4ce9b885..3dc2223bcdffb425f207e7418a8bcc55 int pluginIndex = CraftMagicNumbers.SUPPORTED_API.indexOf(pdf.getAPIVersion()); diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index 52780192d6417f8085566e4cdf3a895a83638520..806894e6db5d78e85c7bad48c0685151c270d0c1 100644 +index d4da9ec6e00bb92b70598ee9a0d0ca5816562378..3e1cf981e77839acee8a825a47102df1ecf8579f 100644 --- a/src/main/java/org/spigotmc/ActivationRange.java +++ b/src/main/java/org/spigotmc/ActivationRange.java @@ -65,26 +65,27 @@ public class ActivationRange private static int checkInactiveWakeup(Entity entity) { - Level world = entity.level; + Level world = entity.level(); + io.papermc.paper.threadedregions.RegionizedWorldData worldData = world.getCurrentWorldData(); // Folia - threaded regions SpigotWorldConfig config = world.spigotConfig; - long inactiveFor = MinecraftServer.currentTick - entity.activatedTick; diff --git a/patches/server/0004-Make-ChunkStatus.EMPTY-not-rely-on-the-main-thread-f.patch b/patches/server/0004-Make-ChunkStatus.EMPTY-not-rely-on-the-main-thread-f.patch deleted file mode 100644 index be3f358..0000000 --- a/patches/server/0004-Make-ChunkStatus.EMPTY-not-rely-on-the-main-thread-f.patch +++ /dev/null @@ -1,395 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 16 Feb 2023 16:50:05 -0800 -Subject: [PATCH] Make ChunkStatus.EMPTY not rely on the main thread for - completion - -In order to do this, we need to push the POI consistency checks -to a later status. Since FULL is the only other status that -uses the main thread, it can go there. - -The consistency checks are only really for when a desync occurs, -and so that delaying the check only matters when the chunk data -has desync'd. As long as the desync is sorted before the -chunk is full loaded (i.e before setBlock can occur on -a chunk), it should not matter. - -This change is primarily due to behavioural changes -in the chunk task queue brought by region threading - -which is to split the queue into separate regions. As such, -it is required that in order for the sync load to complete -that the region owning the chunk drain and execute the task -while ticking. However, that is not always possible in -region threading. Thus, removing the main thread reliance allows -the chunk to progress without requiring a tick thread. -Specifically, this allows far sync loads (outside of a specific -regions bounds) to occur without issue - namely with structure -searching. - -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkFullTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkFullTask.java -index fb42d776f15f735fb59e972e00e2b512c23a8387..300700477ee34bc22b31315825c0e40f61070cd5 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkFullTask.java -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkFullTask.java -@@ -2,6 +2,8 @@ package io.papermc.paper.chunk.system.scheduling; - - import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; - import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; -+import com.mojang.logging.LogUtils; -+import io.papermc.paper.chunk.system.poi.PoiChunk; - import net.minecraft.server.level.ChunkMap; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.world.level.chunk.ChunkAccess; -@@ -9,10 +11,13 @@ import net.minecraft.world.level.chunk.ChunkStatus; - import net.minecraft.world.level.chunk.ImposterProtoChunk; - import net.minecraft.world.level.chunk.LevelChunk; - import net.minecraft.world.level.chunk.ProtoChunk; -+import org.slf4j.Logger; - import java.lang.invoke.VarHandle; - - public final class ChunkFullTask extends ChunkProgressionTask implements Runnable { - -+ private static final Logger LOGGER = LogUtils.getClassLogger(); -+ - protected final NewChunkHolder chunkHolder; - protected final ChunkAccess fromChunk; - protected final PrioritisedExecutor.PrioritisedTask convertToFullTask; -@@ -35,6 +40,15 @@ public final class ChunkFullTask extends ChunkProgressionTask implements Runnabl - // See Vanilla protoChunkToFullChunk for what this function should be doing - final LevelChunk chunk; - try { -+ // moved from the load from nbt stage into here -+ final PoiChunk poiChunk = this.chunkHolder.getPoiChunk(); -+ if (poiChunk == null) { -+ LOGGER.error("Expected poi chunk to be loaded with chunk for task " + this.toString()); -+ } else { -+ poiChunk.load(); -+ this.world.getPoiManager().checkConsistency(this.fromChunk); -+ } -+ - if (this.fromChunk instanceof ImposterProtoChunk wrappedFull) { - chunk = wrappedFull.getWrapped(); - } else { -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java -index be6f3f6a57668a9bd50d0ea5f2dd2335355b69d6..1f7c146ff0b2a835c818f49da6c1f1411f26aa39 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java -@@ -25,6 +25,7 @@ import org.slf4j.Logger; - import java.lang.invoke.VarHandle; - import java.util.Map; - import java.util.concurrent.atomic.AtomicInteger; -+import java.util.concurrent.atomic.AtomicLong; - import java.util.function.Consumer; - - public final class ChunkLoadTask extends ChunkProgressionTask { -@@ -34,9 +35,11 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - private final NewChunkHolder chunkHolder; - private final ChunkDataLoadTask loadTask; - -- private boolean cancelled; -+ private volatile boolean cancelled; - private NewChunkHolder.GenericDataLoadTaskCallback entityLoadTask; - private NewChunkHolder.GenericDataLoadTaskCallback poiLoadTask; -+ private GenericDataLoadTask.TaskResult loadResult; -+ private final AtomicInteger taskCountToComplete = new AtomicInteger(3); // one for poi, one for entity, and one for chunk data - - protected ChunkLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, final int chunkZ, - final NewChunkHolder chunkHolder, final PrioritisedExecutor.Priority priority) { -@@ -44,10 +47,18 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - this.chunkHolder = chunkHolder; - this.loadTask = new ChunkDataLoadTask(scheduler, world, chunkX, chunkZ, priority); - this.loadTask.addCallback((final GenericDataLoadTask.TaskResult result) -> { -- ChunkLoadTask.this.complete(result == null ? null : result.left(), result == null ? null : result.right()); -+ ChunkLoadTask.this.loadResult = result; // must be before getAndDecrement -+ ChunkLoadTask.this.tryCompleteLoad(); - }); - } - -+ private void tryCompleteLoad() { -+ if (this.taskCountToComplete.decrementAndGet() == 0) { -+ final GenericDataLoadTask.TaskResult result = this.cancelled ? null : this.loadResult; // only after the getAndDecrement -+ ChunkLoadTask.this.complete(result == null ? null : result.left(), result == null ? null : result.right()); -+ } -+ } -+ - @Override - public ChunkStatus getTargetStatus() { - return ChunkStatus.EMPTY; -@@ -65,11 +76,8 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - final NewChunkHolder.GenericDataLoadTaskCallback entityLoadTask; - final NewChunkHolder.GenericDataLoadTaskCallback poiLoadTask; - -- final AtomicInteger count = new AtomicInteger(); - final Consumer> scheduleLoadTask = (final GenericDataLoadTask.TaskResult result) -> { -- if (count.decrementAndGet() == 0) { -- ChunkLoadTask.this.loadTask.schedule(false); -- } -+ ChunkLoadTask.this.tryCompleteLoad(); - }; - - // NOTE: it is IMPOSSIBLE for getOrLoadEntityData/getOrLoadPoiData to complete synchronously, because -@@ -85,16 +93,16 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - } - if (!this.chunkHolder.isEntityChunkNBTLoaded()) { - entityLoadTask = this.chunkHolder.getOrLoadEntityData((Consumer)scheduleLoadTask); -- count.setPlain(count.getPlain() + 1); - } else { - entityLoadTask = null; -+ this.taskCountToComplete.getAndDecrement(); // we know the chunk load is not done here, as it is not scheduled - } - - if (!this.chunkHolder.isPoiChunkLoaded()) { - poiLoadTask = this.chunkHolder.getOrLoadPoiData((Consumer)scheduleLoadTask); -- count.setPlain(count.getPlain() + 1); - } else { - poiLoadTask = null; -+ this.taskCountToComplete.getAndDecrement(); // we know the chunk load is not done here, as it is not scheduled - } - - this.entityLoadTask = entityLoadTask; -@@ -107,14 +115,11 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - entityLoadTask.schedule(); - } - -- if (poiLoadTask != null) { -+ if (poiLoadTask != null) { - poiLoadTask.schedule(); - } - -- if (entityLoadTask == null && poiLoadTask == null) { -- // no need to wait on those, we can schedule now -- this.loadTask.schedule(false); -- } -+ this.loadTask.schedule(false); - } - - @Override -@@ -129,15 +134,20 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - - /* - Note: The entityLoadTask/poiLoadTask do not complete when cancelled, -- but this is fine because if they are successfully cancelled then -- we will successfully cancel the load task, which will complete when cancelled -+ so we need to manually try to complete in those cases -+ It is also important to note that we set the cancelled field first, just in case -+ the chunk load task attempts to complete with a non-null value - */ - - if (this.entityLoadTask != null) { -- this.entityLoadTask.cancel(); -+ if (this.entityLoadTask.cancel()) { -+ this.tryCompleteLoad(); -+ } - } - if (this.poiLoadTask != null) { -- this.poiLoadTask.cancel(); -+ if (this.poiLoadTask.cancel()) { -+ this.tryCompleteLoad(); -+ } - } - this.loadTask.cancel(); - } -@@ -249,7 +259,7 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - } - } - -- public final class ChunkDataLoadTask extends CallbackDataLoadTask { -+ public static final class ChunkDataLoadTask extends CallbackDataLoadTask { - protected ChunkDataLoadTask(final ChunkTaskScheduler scheduler, final ServerLevel world, final int chunkX, - final int chunkZ, final PrioritisedExecutor.Priority priority) { - super(scheduler, world, chunkX, chunkZ, RegionFileIOThread.RegionFileType.CHUNK_DATA, priority); -@@ -262,7 +272,7 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - - @Override - protected boolean hasOnMain() { -- return true; -+ return false; - } - - @Override -@@ -272,35 +282,30 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - - @Override - protected PrioritisedExecutor.PrioritisedTask createOnMain(final Runnable run, final PrioritisedExecutor.Priority priority) { -- return this.scheduler.createChunkTask(this.chunkX, this.chunkZ, run, priority); -+ throw new UnsupportedOperationException(); - } - - @Override -- protected TaskResult completeOnMainOffMain(final ChunkSerializer.InProgressChunkHolder data, final Throwable throwable) { -- if (data != null) { -- return null; -- } -- -- final PoiChunk poiChunk = ChunkLoadTask.this.chunkHolder.getPoiChunk(); -- if (poiChunk == null) { -- LOGGER.error("Expected poi chunk to be loaded with chunk for task " + this.toString()); -- } else if (!poiChunk.isLoaded()) { -- // need to call poiChunk.load() on main -- return null; -- } -+ protected TaskResult completeOnMainOffMain(final ChunkAccess data, final Throwable throwable) { -+ throw new UnsupportedOperationException(); -+ } - -- return new TaskResult<>(this.getEmptyChunk(), null); -+ private ProtoChunk getEmptyChunk() { -+ return new ProtoChunk( -+ new ChunkPos(this.chunkX, this.chunkZ), UpgradeData.EMPTY, this.world, -+ this.world.registryAccess().registryOrThrow(Registries.BIOME), (BlendingData)null -+ ); - } - - @Override -- protected TaskResult runOffMain(final CompoundTag data, final Throwable throwable) { -+ protected TaskResult runOffMain(final CompoundTag data, final Throwable throwable) { - if (throwable != null) { - LOGGER.error("Failed to load chunk data for task: " + this.toString() + ", chunk data will be lost", throwable); -- return new TaskResult<>(null, null); -+ return new TaskResult<>(this.getEmptyChunk(), null); - } - - if (data == null) { -- return new TaskResult<>(null, null); -+ return new TaskResult<>(this.getEmptyChunk(), null); - } - - // need to convert data, and then deserialize it -@@ -319,53 +324,18 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - this.world, chunkMap.getPoiManager(), chunkPos, converted, true - ); - -- return new TaskResult<>(chunkHolder, null); -+ return new TaskResult<>(chunkHolder.protoChunk, null); - } catch (final ThreadDeath death) { - throw death; - } catch (final Throwable thr2) { - LOGGER.error("Failed to parse chunk data for task: " + this.toString() + ", chunk data will be lost", thr2); -- return new TaskResult<>(null, thr2); -+ return new TaskResult<>(this.getEmptyChunk(), null); - } - } - -- private ProtoChunk getEmptyChunk() { -- return new ProtoChunk( -- new ChunkPos(this.chunkX, this.chunkZ), UpgradeData.EMPTY, this.world, -- this.world.registryAccess().registryOrThrow(Registries.BIOME), (BlendingData)null -- ); -- } -- - @Override -- protected TaskResult runOnMain(final ChunkSerializer.InProgressChunkHolder data, final Throwable throwable) { -- final PoiChunk poiChunk = ChunkLoadTask.this.chunkHolder.getPoiChunk(); -- if (poiChunk == null) { -- LOGGER.error("Expected poi chunk to be loaded with chunk for task " + this.toString()); -- } else { -- poiChunk.load(); -- } -- -- if (data == null || data.protoChunk == null) { -- // throwable could be non-null, but the off-main task will print its exceptions - so we don't need to care, -- // it's handled already -- -- return new TaskResult<>(this.getEmptyChunk(), null); -- } -- -- // have tasks to run (at this point, it's just the POI consistency checking) -- try { -- if (data.tasks != null) { -- for (int i = 0, len = data.tasks.size(); i < len; ++i) { -- data.tasks.poll().run(); -- } -- } -- -- return new TaskResult<>(data.protoChunk, null); -- } catch (final ThreadDeath death) { -- throw death; -- } catch (final Throwable thr2) { -- LOGGER.error("Failed to parse main tasks for task " + this.toString() + ", chunk data will be lost", thr2); -- return new TaskResult<>(this.getEmptyChunk(), null); -- } -+ protected TaskResult runOnMain(final ChunkAccess data, final Throwable throwable) { -+ throw new UnsupportedOperationException(); - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -index 8950b220b9a3512cd4667beb7bdec0e82e07edc6..9be85eb0abec02bc0e0eded71c34ab1c565c63e7 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -@@ -328,6 +328,12 @@ public class PoiManager extends SectionStorage { - } - } - } -+ -+ public void checkConsistency(net.minecraft.world.level.chunk.ChunkAccess chunk) { -+ for (LevelChunkSection section : chunk.getSections()) { -+ this.checkConsistencyWithBlocks(chunk.getPos(), section); -+ } -+ } - // Paper end - rewrite chunk system - - public void checkConsistencyWithBlocks(ChunkPos chunkPos, LevelChunkSection chunkSection) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 0ec80b83a99bfdb1f985045d98a81905a8a5a3ac..9d6f4749ed72fe319754ccea28f20fa97286527d 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -122,13 +122,11 @@ public class ChunkSerializer { - public static final class InProgressChunkHolder { - - public final ProtoChunk protoChunk; -- public final java.util.ArrayDeque tasks; - - public CompoundTag poiData; - -- public InProgressChunkHolder(final ProtoChunk protoChunk, final java.util.ArrayDeque tasks) { -+ public InProgressChunkHolder(final ProtoChunk protoChunk) { - this.protoChunk = protoChunk; -- this.tasks = tasks; - } - } - // Paper end -@@ -136,7 +134,6 @@ public class ChunkSerializer { - public static ProtoChunk read(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt) { - // Paper start - add variant for async calls - InProgressChunkHolder holder = loadChunk(world, poiStorage, chunkPos, nbt, true); -- holder.tasks.forEach(Runnable::run); - return holder.protoChunk; - } - -@@ -145,7 +142,6 @@ public class ChunkSerializer { - private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion"); - // Paper end - public static InProgressChunkHolder loadChunk(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt, boolean distinguish) { -- java.util.ArrayDeque tasksToExecuteOnMain = new java.util.ArrayDeque<>(); - // Paper end - // Paper start - Do NOT attempt to load chunks saved with newer versions - if (nbt.contains("DataVersion", 99)) { -@@ -223,9 +219,7 @@ public class ChunkSerializer { - LevelChunkSection chunksection = new LevelChunkSection(b0, datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write - - achunksection[k] = chunksection; -- tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main -- poiStorage.checkConsistencyWithBlocks(chunkPos, chunksection); -- }); // Paper - delay this task since we're executing off-main -+ // Paper - rewrite chunk system - moved to final load stage - } - - boolean flag3 = nbttagcompound1.contains("BlockLight", 7); -@@ -403,7 +397,7 @@ public class ChunkSerializer { - } - - if (chunkstatus_type == ChunkStatus.ChunkType.LEVELCHUNK) { -- return new InProgressChunkHolder(new ImposterProtoChunk((LevelChunk) object1, false), tasksToExecuteOnMain); // Paper - Async chunk loading -+ return new InProgressChunkHolder(new ImposterProtoChunk((LevelChunk) object1, false)); // Paper - Async chunk loading - } else { - ProtoChunk protochunk1 = (ProtoChunk) object1; - -@@ -446,7 +440,7 @@ public class ChunkSerializer { - protochunk1.setCarvingMask(worldgenstage_features, new CarvingMask(nbttagcompound4.getLongArray(s1), ((ChunkAccess) object1).getMinBuildHeight())); - } - -- return new InProgressChunkHolder(protochunk1, tasksToExecuteOnMain); // Paper - Async chunk loading -+ return new InProgressChunkHolder(protochunk1); // Paper - Async chunk loading - } - } - diff --git a/patches/server/0008-Max-pending-logins.patch b/patches/server/0004-Max-pending-logins.patch similarity index 96% rename from patches/server/0008-Max-pending-logins.patch rename to patches/server/0004-Max-pending-logins.patch index d7f2436..0b8cfae 100644 --- a/patches/server/0008-Max-pending-logins.patch +++ b/patches/server/0004-Max-pending-logins.patch @@ -19,7 +19,7 @@ index 2e96377d628b3a07fb565020074d665f594f32e8..75b1877f8c3e4da3183437f327ef3376 } // Folia - region threading - remove delayed accept diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 7d99233f4b36d699aae81be554e78313909c9a7d..7416d8eed50ef7156db158c05c89d6062193bf2e 100644 +index 576e60f2ce00733254895f83f4bef76152dc3fe9..f35192cd5f08fdac6eaee549d6b73cccd22fefe4 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -180,6 +180,17 @@ public abstract class PlayerList { diff --git a/patches/server/0009-Add-chunk-system-throughput-counters-to-tps.patch b/patches/server/0005-Add-chunk-system-throughput-counters-to-tps.patch similarity index 100% rename from patches/server/0009-Add-chunk-system-throughput-counters-to-tps.patch rename to patches/server/0005-Add-chunk-system-throughput-counters-to-tps.patch diff --git a/patches/server/0006-Increase-parallelism-for-neighbour-writing-chunk-sta.patch b/patches/server/0006-Increase-parallelism-for-neighbour-writing-chunk-sta.patch deleted file mode 100644 index d8a09b0..0000000 --- a/patches/server/0006-Increase-parallelism-for-neighbour-writing-chunk-sta.patch +++ /dev/null @@ -1,1009 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 26 Feb 2023 23:42:29 -0800 -Subject: [PATCH] Increase parallelism for neighbour writing chunk statuses - -Namely, everything after FEATURES. By creating a dependency -chain indicating what chunks are in use, we can safely -schedule completely independent tasks in parallel. This -will allow the chunk system to scale beyond 10 threads -per world. - -diff --git a/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java -index 88c48279d04b2bc5a67de0fdbd2c266516cbcd49..d52a522fe6d5c4375862691fa32450bc458ca38b 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java -+++ b/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java -@@ -286,7 +286,92 @@ public class RegionizedPlayerChunkLoader { - } - } - -- return chunks.toLongArray(); -+ // to increase generation parallelism, we want to space the chunks out so that they are not nearby when generating -+ // this also means we are minimising locality -+ // but, we need to maintain sorted order by manhatten distance -+ -+ // first, build a map of manhatten distance -> chunks -+ final java.util.List byDistance = new java.util.ArrayList<>(); -+ for (final it.unimi.dsi.fastutil.longs.LongIterator iterator = chunks.iterator(); iterator.hasNext();) { -+ final long chunkKey = iterator.nextLong(); -+ -+ final int chunkX = CoordinateUtils.getChunkX(chunkKey); -+ final int chunkZ = CoordinateUtils.getChunkZ(chunkKey); -+ -+ final int dist = Math.abs(chunkX) + Math.abs(chunkZ); -+ if (dist == byDistance.size()) { -+ final LongArrayList list = new LongArrayList(); -+ list.add(chunkKey); -+ byDistance.add(list); -+ continue; -+ } -+ -+ byDistance.get(dist).add(chunkKey); -+ } -+ -+ // per distance we transform the chunk list so that each element is maximally spaced out from each other -+ for (int i = 0, len = byDistance.size(); i < len; ++i) { -+ final LongArrayList notAdded = byDistance.get(i).clone(); -+ final LongArrayList added = new LongArrayList(); -+ -+ while (!notAdded.isEmpty()) { -+ if (added.isEmpty()) { -+ added.add(notAdded.removeLong(notAdded.size() - 1)); -+ continue; -+ } -+ -+ long maxChunk = -1L; -+ int maxDist = 0; -+ -+ // select the chunk from the not yet added set that has the largest minimum distance from -+ // the current set of added chunks -+ -+ for (final it.unimi.dsi.fastutil.longs.LongIterator iterator = notAdded.iterator(); iterator.hasNext();) { -+ final long chunkKey = iterator.nextLong(); -+ final int chunkX = CoordinateUtils.getChunkX(chunkKey); -+ final int chunkZ = CoordinateUtils.getChunkZ(chunkKey); -+ -+ int minDist = Integer.MAX_VALUE; -+ -+ for (final it.unimi.dsi.fastutil.longs.LongIterator iterator2 = added.iterator(); iterator2.hasNext();) { -+ final long addedKey = iterator2.nextLong(); -+ final int addedX = CoordinateUtils.getChunkX(addedKey); -+ final int addedZ = CoordinateUtils.getChunkZ(addedKey); -+ -+ // here we use square distance because chunk generation uses neighbours in a square radius -+ final int dist = Math.max(Math.abs(addedX - chunkX), Math.abs(addedZ - chunkZ)); -+ -+ if (dist < minDist) { -+ minDist = dist; -+ } -+ } -+ -+ if (minDist > maxDist) { -+ maxDist = minDist; -+ maxChunk = chunkKey; -+ } -+ } -+ -+ // move the selected chunk from the not added set to the added set -+ -+ if (!notAdded.rem(maxChunk)) { -+ throw new IllegalStateException(); -+ } -+ -+ added.add(maxChunk); -+ } -+ -+ byDistance.set(i, added); -+ } -+ -+ // now, rebuild the list so that it still maintains manhatten distance order -+ final LongArrayList ret = new LongArrayList(chunks.size()); -+ -+ for (final LongArrayList dist : byDistance) { -+ ret.addAll(dist); -+ } -+ -+ return ret.toLongArray(); - } - - public static final class PlayerChunkLoaderData { -diff --git a/src/main/java/io/papermc/paper/chunk/system/light/LightQueue.java b/src/main/java/io/papermc/paper/chunk/system/light/LightQueue.java -index 0b7a2b0ead4f3bc07bfd9a38c2b7cf024bd140c6..36e93fefdfbebddce4c153974c7cd81af3cb92e9 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/light/LightQueue.java -+++ b/src/main/java/io/papermc/paper/chunk/system/light/LightQueue.java -@@ -4,7 +4,6 @@ import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; - import ca.spottedleaf.starlight.common.light.BlockStarLightEngine; - import ca.spottedleaf.starlight.common.light.SkyStarLightEngine; - import ca.spottedleaf.starlight.common.light.StarLightInterface; --import io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler; - import io.papermc.paper.util.CoordinateUtils; - import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; - import it.unimi.dsi.fastutil.shorts.ShortCollection; -@@ -13,6 +12,7 @@ import net.minecraft.core.BlockPos; - import net.minecraft.core.SectionPos; - import net.minecraft.server.level.ServerLevel; - import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.ChunkStatus; - import java.util.ArrayList; - import java.util.HashSet; - import java.util.List; -@@ -201,7 +201,10 @@ public final class LightQueue { - this.chunkCoordinate = chunkCoordinate; - this.lightEngine = lightEngine; - this.queue = queue; -- this.task = queue.world.chunkTaskScheduler.lightExecutor.createTask(this, priority); -+ this.task = queue.world.chunkTaskScheduler.radiusAwareScheduler.createTask( -+ CoordinateUtils.getChunkX(chunkCoordinate), CoordinateUtils.getChunkZ(chunkCoordinate), -+ ChunkStatus.LIGHT.writeRadius, this, priority -+ ); - } - - public void schedule() { -@@ -230,23 +233,23 @@ public final class LightQueue { - - @Override - public void run() { -- final SkyStarLightEngine skyEngine = this.lightEngine.getSkyLightEngine(); -- final BlockStarLightEngine blockEngine = this.lightEngine.getBlockLightEngine(); -- try { -- synchronized (this.queue) { -- this.queue.chunkTasks.remove(this.chunkCoordinate); -- } -+ synchronized (this.queue) { -+ this.queue.chunkTasks.remove(this.chunkCoordinate); -+ } - -- boolean litChunk = false; -- if (this.lightTasks != null) { -- for (final BooleanSupplier run : this.lightTasks) { -- if (run.getAsBoolean()) { -- litChunk = true; -- break; -- } -+ boolean litChunk = false; -+ if (this.lightTasks != null) { -+ for (final BooleanSupplier run : this.lightTasks) { -+ if (run.getAsBoolean()) { -+ litChunk = true; -+ break; - } - } -+ } - -+ final SkyStarLightEngine skyEngine = this.lightEngine.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.lightEngine.getBlockLightEngine(); -+ try { - final long coordinate = this.chunkCoordinate; - 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 1e5df2593b21b8ee7636f5df28541f9b04afa1e6..db18f2947ef9d2863e3a029f0500343920cba1db 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 -@@ -1346,17 +1346,23 @@ public final class ChunkHolderManager { - } - - public Boolean tryDrainTicketUpdates() { -- final boolean acquired = this.ticketLock.tryLock(); -- try { -- if (!acquired) { -- return null; -- } -+ boolean ret = false; -+ for (;;) { -+ final boolean acquired = this.ticketLock.tryLock(); -+ try { -+ if (!acquired) { -+ return ret ? Boolean.TRUE : null; -+ } - -- return Boolean.valueOf(this.drainTicketUpdates()); -- } finally { -- if (acquired) { -- this.ticketLock.unlock(); -+ ret |= this.drainTicketUpdates(); -+ } finally { -+ if (acquired) { -+ this.ticketLock.unlock(); -+ } - } -+ if (this.delayedTicketUpdates.isEmpty()) { -+ return Boolean.valueOf(ret); -+ } // else: try to re-acquire - } - } - -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java -index c185a85e18932ac8ee96a01779e1e1a580197613..5ca4e9c85c957c669d54fd9e5e52f13502b592da 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java -@@ -2,9 +2,9 @@ package io.papermc.paper.chunk.system.scheduling; - - import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; - import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool; --import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadedTaskQueue; - import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; - import com.mojang.logging.LogUtils; -+import io.papermc.paper.chunk.system.scheduling.queue.RadiusAwarePrioritisedExecutor; - import io.papermc.paper.configuration.GlobalConfiguration; - import io.papermc.paper.util.CoordinateUtils; - import io.papermc.paper.util.TickThread; -@@ -21,7 +21,6 @@ import net.minecraft.world.level.ChunkPos; - import net.minecraft.world.level.chunk.ChunkAccess; - import net.minecraft.world.level.chunk.ChunkStatus; - import net.minecraft.world.level.chunk.LevelChunk; --import org.bukkit.Bukkit; - import org.slf4j.Logger; - import java.io.File; - import java.util.ArrayDeque; -@@ -34,7 +33,6 @@ import java.util.Objects; - import java.util.concurrent.atomic.AtomicBoolean; - import java.util.concurrent.atomic.AtomicLong; - import java.util.concurrent.locks.ReentrantLock; --import java.util.function.BooleanSupplier; - import java.util.function.Consumer; - - public final class ChunkTaskScheduler { -@@ -112,9 +110,9 @@ public final class ChunkTaskScheduler { - - public final ServerLevel world; - public final PrioritisedThreadPool workers; -- public final PrioritisedThreadPool.PrioritisedPoolExecutor lightExecutor; -- public final PrioritisedThreadPool.PrioritisedPoolExecutor genExecutor; -+ public final RadiusAwarePrioritisedExecutor radiusAwareScheduler; - public final PrioritisedThreadPool.PrioritisedPoolExecutor parallelGenExecutor; -+ private final PrioritisedThreadPool.PrioritisedPoolExecutor radiusAwareGenExecutor; - public final PrioritisedThreadPool.PrioritisedPoolExecutor loadExecutor; - - // Folia - regionised ticking -@@ -132,7 +130,7 @@ public final class ChunkTaskScheduler { - ChunkStatus.CARVERS.writeRadius = 0; - ChunkStatus.LIQUID_CARVERS.writeRadius = 0; - ChunkStatus.FEATURES.writeRadius = 1; -- ChunkStatus.LIGHT.writeRadius = 1; -+ ChunkStatus.LIGHT.writeRadius = 2; - ChunkStatus.SPAWN.writeRadius = 0; - ChunkStatus.HEIGHTMAPS.writeRadius = 0; - ChunkStatus.FULL.writeRadius = 0; -@@ -200,12 +198,11 @@ public final class ChunkTaskScheduler { - this.workers = workers; - - final String worldName = world.getWorld().getName(); -- this.genExecutor = workers.createExecutor("Chunk single-threaded generation executor for world '" + worldName + "'", 1); -- // same as genExecutor, as there are race conditions between updating blocks in FEATURE status while lighting chunks -- this.lightExecutor = this.genExecutor; -- this.parallelGenExecutor = newChunkSystemGenParallelism <= 1 ? this.genExecutor -- : workers.createExecutor("Chunk parallel generation executor for world '" + worldName + "'", newChunkSystemGenParallelism); -+ this.parallelGenExecutor = workers.createExecutor("Chunk parallel generation executor for world '" + worldName + "'", Math.max(1, newChunkSystemGenParallelism)); -+ this.radiusAwareGenExecutor = -+ newChunkSystemGenParallelism <= 1 ? this.parallelGenExecutor : workers.createExecutor("Chunk radius aware generator for world '" + worldName + "'", newChunkSystemGenParallelism); - this.loadExecutor = workers.createExecutor("Chunk load executor for world '" + worldName + "'", newChunkSystemLoadParallelism); -+ this.radiusAwareScheduler = new RadiusAwarePrioritisedExecutor(this.radiusAwareGenExecutor, Math.max(1, newChunkSystemGenParallelism)); - this.chunkHolderManager = new ChunkHolderManager(world, this); - } - -@@ -744,8 +741,7 @@ public final class ChunkTaskScheduler { - // Folia - regionised ticking - - public boolean halt(final boolean sync, final long maxWaitNS) { -- this.lightExecutor.halt(); -- this.genExecutor.halt(); -+ this.radiusAwareGenExecutor.halt(); - this.parallelGenExecutor.halt(); - this.loadExecutor.halt(); - final long time = System.nanoTime(); -@@ -753,8 +749,7 @@ public final class ChunkTaskScheduler { - // start at 10 * 0.5ms -> 5ms - for (long failures = 9L;; failures = ConcurrentUtil.linearLongBackoff(failures, 500_000L, 50_000_000L)) { - if ( -- !this.lightExecutor.isActive() && -- !this.genExecutor.isActive() && -+ !this.radiusAwareGenExecutor.isActive() && - !this.parallelGenExecutor.isActive() && - !this.loadExecutor.isActive() - ) { -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkUpgradeGenericStatusTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkUpgradeGenericStatusTask.java -index 73ce0909bd89244835a0d0f2030a25871461f1e0..ecc366a4176b2efadc46aa91aa21621f0fc6abe9 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkUpgradeGenericStatusTask.java -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkUpgradeGenericStatusTask.java -@@ -39,8 +39,11 @@ public final class ChunkUpgradeGenericStatusTask extends ChunkProgressionTask im - this.fromStatus = chunk.getStatus(); - this.toStatus = toStatus; - this.neighbours = neighbours; -- this.generateTask = (this.toStatus.isParallelCapable ? this.scheduler.parallelGenExecutor : this.scheduler.genExecutor) -- .createTask(this, priority); -+ if (this.toStatus.isParallelCapable) { -+ this.generateTask = this.scheduler.parallelGenExecutor.createTask(this, priority); -+ } else { -+ this.generateTask = this.scheduler.radiusAwareScheduler.createTask(chunkX, chunkZ, this.toStatus.writeRadius, this, priority); -+ } - } - - @Override -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/queue/RadiusAwarePrioritisedExecutor.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/queue/RadiusAwarePrioritisedExecutor.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3272f73013ea7d4efdd0ae2903925cc543be7075 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/queue/RadiusAwarePrioritisedExecutor.java -@@ -0,0 +1,668 @@ -+package io.papermc.paper.chunk.system.scheduling.queue; -+ -+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor; -+import io.papermc.paper.util.CoordinateUtils; -+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+import java.util.ArrayList; -+import java.util.Comparator; -+import java.util.List; -+import java.util.PriorityQueue; -+ -+public class RadiusAwarePrioritisedExecutor { -+ -+ private static final Comparator DEPENDENCY_NODE_COMPARATOR = (final DependencyNode t1, final DependencyNode t2) -> { -+ return Long.compare(t1.id, t2.id); -+ }; -+ -+ private final DependencyTree[] queues = new DependencyTree[PrioritisedExecutor.Priority.TOTAL_SCHEDULABLE_PRIORITIES]; -+ private static final int NO_TASKS_QUEUED = -1; -+ private int selectedQueue = NO_TASKS_QUEUED; -+ private boolean canQueueTasks = true; -+ -+ public RadiusAwarePrioritisedExecutor(final PrioritisedExecutor executor, final int maxToSchedule) { -+ for (int i = 0; i < this.queues.length; ++i) { -+ this.queues[i] = new DependencyTree(this, executor, maxToSchedule, i); -+ } -+ } -+ -+ private boolean canQueueTasks() { -+ return this.canQueueTasks; -+ } -+ -+ private List treeFinished() { -+ this.canQueueTasks = true; -+ for (int priority = 0; priority < this.queues.length; ++priority) { -+ final DependencyTree queue = this.queues[priority]; -+ if (queue.hasWaitingTasks()) { -+ final List ret = queue.tryPushTasks(); -+ -+ if (ret == null || ret.isEmpty()) { -+ // this happens when the tasks in the wait queue were purged -+ // in this case, the queue was actually empty, we just had to purge it -+ // if we set the selected queue without scheduling any tasks, the queue will never be unselected -+ // as that requires a scheduled task completing... -+ continue; -+ } -+ -+ this.selectedQueue = priority; -+ return ret; -+ } -+ } -+ -+ this.selectedQueue = NO_TASKS_QUEUED; -+ -+ return null; -+ } -+ -+ private List queue(final Task task, final PrioritisedExecutor.Priority priority) { -+ final int priorityId = priority.priority; -+ final DependencyTree queue = this.queues[priorityId]; -+ -+ final DependencyNode node = new DependencyNode(task, queue); -+ -+ if (task.dependencyNode != null) { -+ throw new IllegalStateException(); -+ } -+ task.dependencyNode = node; -+ -+ queue.pushNode(node); -+ -+ if (this.selectedQueue == NO_TASKS_QUEUED) { -+ this.canQueueTasks = true; -+ this.selectedQueue = priorityId; -+ return queue.tryPushTasks(); -+ } -+ -+ if (!this.canQueueTasks) { -+ return null; -+ } -+ -+ if (PrioritisedExecutor.Priority.isHigherPriority(priorityId, this.selectedQueue)) { -+ // prevent the lower priority tree from queueing more tasks -+ this.canQueueTasks = false; -+ return null; -+ } -+ -+ // priorityId != selectedQueue: lower priority, don't care - treeFinished will pick it up -+ return priorityId == this.selectedQueue ? queue.tryPushTasks() : null; -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, final int radius, -+ final Runnable run, final PrioritisedExecutor.Priority priority) { -+ if (radius < 0) { -+ throw new IllegalArgumentException("Radius must be > 0: " + radius); -+ } -+ return new Task(this, chunkX, chunkZ, radius, run, priority); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createTask(final int chunkX, final int chunkZ, final int radius, -+ final Runnable run) { -+ return this.createTask(chunkX, chunkZ, radius, run, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask queueTask(final int chunkX, final int chunkZ, final int radius, -+ final Runnable run, final PrioritisedExecutor.Priority priority) { -+ final PrioritisedExecutor.PrioritisedTask ret = this.createTask(chunkX, chunkZ, radius, run, priority); -+ -+ ret.queue(); -+ -+ return ret; -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask queueTask(final int chunkX, final int chunkZ, final int radius, -+ final Runnable run) { -+ final PrioritisedExecutor.PrioritisedTask ret = this.createTask(chunkX, chunkZ, radius, run); -+ -+ ret.queue(); -+ -+ return ret; -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ return new Task(this, 0, 0, -1, run, priority); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask createInfiniteRadiusTask(final Runnable run) { -+ return this.createInfiniteRadiusTask(run, PrioritisedExecutor.Priority.NORMAL); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run, final PrioritisedExecutor.Priority priority) { -+ final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, priority); -+ -+ ret.queue(); -+ -+ return ret; -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask queueInfiniteRadiusTask(final Runnable run) { -+ final PrioritisedExecutor.PrioritisedTask ret = this.createInfiniteRadiusTask(run, PrioritisedExecutor.Priority.NORMAL); -+ -+ ret.queue(); -+ -+ return ret; -+ } -+ -+ // all accesses must be synchronised by the radius aware object -+ private static final class DependencyTree { -+ -+ private final RadiusAwarePrioritisedExecutor scheduler; -+ private final PrioritisedExecutor executor; -+ private final int maxToSchedule; -+ private final int treeIndex; -+ -+ private int currentlyExecuting; -+ private long idGenerator; -+ -+ private final PriorityQueue awaiting = new PriorityQueue<>(DEPENDENCY_NODE_COMPARATOR); -+ -+ private final PriorityQueue infiniteRadius = new PriorityQueue<>(DEPENDENCY_NODE_COMPARATOR); -+ private boolean isInfiniteRadiusScheduled; -+ -+ private final Long2ReferenceOpenHashMap nodeByPosition = new Long2ReferenceOpenHashMap<>(); -+ -+ public DependencyTree(final RadiusAwarePrioritisedExecutor scheduler, final PrioritisedExecutor executor, -+ final int maxToSchedule, final int treeIndex) { -+ this.scheduler = scheduler; -+ this.executor = executor; -+ this.maxToSchedule = maxToSchedule; -+ this.treeIndex = treeIndex; -+ } -+ -+ public boolean hasWaitingTasks() { -+ return !this.awaiting.isEmpty() || !this.infiniteRadius.isEmpty(); -+ } -+ -+ private long nextId() { -+ return this.idGenerator++; -+ } -+ -+ private boolean isExecutingAnyTasks() { -+ return this.currentlyExecuting != 0; -+ } -+ -+ private void pushNode(final DependencyNode node) { -+ if (!node.task.isFiniteRadius()) { -+ this.infiniteRadius.add(node); -+ return; -+ } -+ -+ // set up dependency for node -+ final Task task = node.task; -+ -+ final int centerX = task.chunkX; -+ final int centerZ = task.chunkZ; -+ final int radius = task.radius; -+ -+ final int minX = centerX - radius; -+ final int maxX = centerX + radius; -+ -+ final int minZ = centerZ - radius; -+ final int maxZ = centerZ + radius; -+ -+ ReferenceOpenHashSet parents = null; -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final DependencyNode dependency = this.nodeByPosition.put(CoordinateUtils.getChunkKey(currX, currZ), node); -+ if (dependency != null) { -+ if (parents == null) { -+ parents = new ReferenceOpenHashSet<>(); -+ } -+ if (parents.add(dependency)) { -+ // added a dependency, so we need to add as a child to the dependency -+ if (dependency.children == null) { -+ dependency.children = new ArrayList<>(); -+ } -+ dependency.children.add(node); -+ } -+ } -+ } -+ } -+ -+ if (parents == null) { -+ // no dependencies, add straight to awaiting -+ this.awaiting.add(node); -+ } else { -+ node.parents = parents; -+ // we will be added to awaiting once we have no parents -+ } -+ } -+ -+ // called only when a node is returned after being executed -+ private List returnNode(final DependencyNode node) { -+ final Task task = node.task; -+ -+ // now that the task is completed, we can push its children to the awaiting queue -+ this.pushChildren(node); -+ -+ if (task.isFiniteRadius()) { -+ // remove from dependency map -+ this.removeNodeFromMap(node); -+ } else { -+ // mark as no longer executing infinite radius -+ if (!this.isInfiniteRadiusScheduled) { -+ throw new IllegalStateException(); -+ } -+ this.isInfiniteRadiusScheduled = false; -+ } -+ -+ // decrement executing count, we are done executing this task -+ --this.currentlyExecuting; -+ -+ if (this.currentlyExecuting == 0) { -+ return this.scheduler.treeFinished(); -+ } -+ -+ return this.scheduler.canQueueTasks() ? this.tryPushTasks() : null; -+ } -+ -+ private List tryPushTasks() { -+ // tasks are not queued, but only created here - we do hold the lock for the map -+ List ret = null; -+ PrioritisedExecutor.PrioritisedTask pushedTask; -+ while ((pushedTask = this.tryPushTask()) != null) { -+ if (ret == null) { -+ ret = new ArrayList<>(); -+ } -+ ret.add(pushedTask); -+ } -+ -+ return ret; -+ } -+ -+ private void removeNodeFromMap(final DependencyNode node) { -+ final Task task = node.task; -+ -+ final int centerX = task.chunkX; -+ final int centerZ = task.chunkZ; -+ final int radius = task.radius; -+ -+ final int minX = centerX - radius; -+ final int maxX = centerX + radius; -+ -+ final int minZ = centerZ - radius; -+ final int maxZ = centerZ + radius; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ this.nodeByPosition.remove(CoordinateUtils.getChunkKey(currX, currZ), node); -+ } -+ } -+ } -+ -+ private void pushChildren(final DependencyNode node) { -+ // add all the children that we can into awaiting -+ final List children = node.children; -+ if (children != null) { -+ for (int i = 0, len = children.size(); i < len; ++i) { -+ final DependencyNode child = children.get(i); -+ if (!child.parents.remove(node)) { -+ throw new IllegalStateException(); -+ } -+ if (child.parents.isEmpty()) { -+ // no more dependents, we can push to awaiting -+ child.parents = null; -+ // even if the child is purged, we need to push it so that its children will be pushed -+ this.awaiting.add(child); -+ } -+ } -+ } -+ } -+ -+ private DependencyNode pollAwaiting() { -+ final DependencyNode ret = this.awaiting.poll(); -+ if (ret == null) { -+ return ret; -+ } -+ -+ if (ret.parents != null) { -+ throw new IllegalStateException(); -+ } -+ -+ if (ret.purged) { -+ // need to manually remove from state here -+ this.pushChildren(ret); -+ this.removeNodeFromMap(ret); -+ } // else: delay children push until the task has finished -+ -+ return ret; -+ } -+ -+ private DependencyNode pollInfinite() { -+ return this.infiniteRadius.poll(); -+ } -+ -+ public PrioritisedExecutor.PrioritisedTask tryPushTask() { -+ if (this.currentlyExecuting >= this.maxToSchedule || this.isInfiniteRadiusScheduled) { -+ return null; -+ } -+ -+ DependencyNode firstInfinite; -+ while ((firstInfinite = this.infiniteRadius.peek()) != null && firstInfinite.purged) { -+ this.pollInfinite(); -+ } -+ -+ DependencyNode firstAwaiting; -+ while ((firstAwaiting = this.awaiting.peek()) != null && firstAwaiting.purged) { -+ this.pollAwaiting(); -+ } -+ -+ if (firstInfinite == null && firstAwaiting == null) { -+ return null; -+ } -+ -+ // firstAwaiting compared to firstInfinite -+ final int compare; -+ -+ if (firstAwaiting == null) { -+ // we choose first infinite, or infinite < awaiting -+ compare = 1; -+ } else if (firstInfinite == null) { -+ // we choose first awaiting, or awaiting < infinite -+ compare = -1; -+ } else { -+ compare = DEPENDENCY_NODE_COMPARATOR.compare(firstAwaiting, firstInfinite); -+ } -+ -+ if (compare >= 0) { -+ if (this.currentlyExecuting != 0) { -+ // don't queue infinite task while other tasks are executing in parallel -+ return null; -+ } -+ ++this.currentlyExecuting; -+ this.pollInfinite(); -+ this.isInfiniteRadiusScheduled = true; -+ return firstInfinite.task.pushTask(this.executor); -+ } else { -+ ++this.currentlyExecuting; -+ this.pollAwaiting(); -+ return firstAwaiting.task.pushTask(this.executor); -+ } -+ } -+ } -+ -+ private static final class DependencyNode { -+ -+ private final Task task; -+ private final DependencyTree tree; -+ -+ // dependency tree fields -+ // (must hold lock on the scheduler to use) -+ // null is the same as empty, we just use it so that we don't allocate the set unless we need to -+ private List children; -+ // null is the same as empty, indicating that this task is considered "awaiting" -+ private ReferenceOpenHashSet parents; -+ // false -> scheduled and not cancelled -+ // true -> scheduled but cancelled -+ private boolean purged; -+ private final long id; -+ -+ public DependencyNode(final Task task, final DependencyTree tree) { -+ this.task = task; -+ this.id = tree.nextId(); -+ this.tree = tree; -+ } -+ } -+ -+ private static final class Task implements PrioritisedExecutor.PrioritisedTask, Runnable { -+ -+ // task specific fields -+ private final RadiusAwarePrioritisedExecutor scheduler; -+ private final int chunkX; -+ private final int chunkZ; -+ private final int radius; -+ private Runnable run; -+ private PrioritisedExecutor.Priority priority; -+ -+ private DependencyNode dependencyNode; -+ private PrioritisedExecutor.PrioritisedTask queuedTask; -+ -+ private Task(final RadiusAwarePrioritisedExecutor scheduler, final int chunkX, final int chunkZ, final int radius, -+ final Runnable run, final PrioritisedExecutor.Priority priority) { -+ this.scheduler = scheduler; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.radius = radius; -+ this.run = run; -+ this.priority = priority; -+ } -+ -+ private boolean isFiniteRadius() { -+ return this.radius >= 0; -+ } -+ -+ private PrioritisedExecutor.PrioritisedTask pushTask(final PrioritisedExecutor executor) { -+ return this.queuedTask = executor.createTask(this, this.priority); -+ } -+ -+ private void executeTask() { -+ final Runnable run = this.run; -+ this.run = null; -+ run.run(); -+ } -+ -+ private static void scheduleTasks(final List toSchedule) { -+ if (toSchedule != null) { -+ for (int i = 0, len = toSchedule.size(); i < len; ++i) { -+ toSchedule.get(i).queue(); -+ } -+ } -+ } -+ -+ private void returnNode() { -+ final List toSchedule; -+ synchronized (this.scheduler) { -+ final DependencyNode node = this.dependencyNode; -+ this.dependencyNode = null; -+ toSchedule = node.tree.returnNode(node); -+ } -+ -+ scheduleTasks(toSchedule); -+ } -+ -+ @Override -+ public void run() { -+ final Runnable run = this.run; -+ this.run = null; -+ try { -+ run.run(); -+ } finally { -+ this.returnNode(); -+ } -+ } -+ -+ @Override -+ public boolean queue() { -+ final List toSchedule; -+ synchronized (this.scheduler) { -+ if (this.queuedTask != null || this.dependencyNode != null || this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ toSchedule = this.scheduler.queue(this, this.priority); -+ } -+ -+ scheduleTasks(toSchedule); -+ return true; -+ } -+ -+ @Override -+ public boolean cancel() { -+ final PrioritisedExecutor.PrioritisedTask task; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ this.priority = PrioritisedExecutor.Priority.COMPLETING; -+ if (this.dependencyNode != null) { -+ this.dependencyNode.purged = true; -+ this.dependencyNode = null; -+ } -+ -+ return true; -+ } -+ } -+ -+ if (task.cancel()) { -+ // must manually return the node -+ this.run = null; -+ this.returnNode(); -+ return true; -+ } -+ return false; -+ } -+ -+ @Override -+ public boolean execute() { -+ final PrioritisedExecutor.PrioritisedTask task; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ this.priority = PrioritisedExecutor.Priority.COMPLETING; -+ if (this.dependencyNode != null) { -+ this.dependencyNode.purged = true; -+ this.dependencyNode = null; -+ } -+ // fall through to execution logic -+ } -+ } -+ -+ if (task != null) { -+ // will run the return node logic automatically -+ return task.execute(); -+ } else { -+ // don't run node removal/insertion logic, we aren't actually removed from the dependency tree -+ this.executeTask(); -+ return true; -+ } -+ } -+ -+ @Override -+ public PrioritisedExecutor.Priority getPriority() { -+ final PrioritisedExecutor.PrioritisedTask task; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ return this.priority; -+ } -+ } -+ -+ return task.getPriority(); -+ } -+ -+ @Override -+ public boolean setPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ final PrioritisedExecutor.PrioritisedTask task; -+ List toSchedule = null; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ if (this.priority == priority) { -+ return true; -+ } -+ -+ this.priority = priority; -+ if (this.dependencyNode != null) { -+ // need to re-insert node -+ this.dependencyNode.purged = true; -+ this.dependencyNode = null; -+ toSchedule = this.scheduler.queue(this, priority); -+ } -+ } -+ } -+ -+ if (task != null) { -+ return task.setPriority(priority); -+ } -+ -+ scheduleTasks(toSchedule); -+ -+ return true; -+ } -+ -+ @Override -+ public boolean raisePriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ final PrioritisedExecutor.PrioritisedTask task; -+ List toSchedule = null; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ if (this.priority.isHigherOrEqualPriority(priority)) { -+ return true; -+ } -+ -+ this.priority = priority; -+ if (this.dependencyNode != null) { -+ // need to re-insert node -+ this.dependencyNode.purged = true; -+ this.dependencyNode = null; -+ toSchedule = this.scheduler.queue(this, priority); -+ } -+ } -+ } -+ -+ if (task != null) { -+ return task.raisePriority(priority); -+ } -+ -+ scheduleTasks(toSchedule); -+ -+ return true; -+ } -+ -+ @Override -+ public boolean lowerPriority(final PrioritisedExecutor.Priority priority) { -+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) { -+ throw new IllegalArgumentException("Invalid priority " + priority); -+ } -+ -+ final PrioritisedExecutor.PrioritisedTask task; -+ List toSchedule = null; -+ synchronized (this.scheduler) { -+ if ((task = this.queuedTask) == null) { -+ if (this.priority == PrioritisedExecutor.Priority.COMPLETING) { -+ return false; -+ } -+ -+ if (this.priority.isLowerOrEqualPriority(priority)) { -+ return true; -+ } -+ -+ this.priority = priority; -+ if (this.dependencyNode != null) { -+ // need to re-insert node -+ this.dependencyNode.purged = true; -+ this.dependencyNode = null; -+ toSchedule = this.scheduler.queue(this, priority); -+ } -+ } -+ } -+ -+ if (task != null) { -+ return task.lowerPriority(priority); -+ } -+ -+ scheduleTasks(toSchedule); -+ -+ return true; -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -index b64fc5adda3a9ab793a074d08df822754bd8d186..dd9d124e9757e8dec5bd28b3840751ac15304ed9 100644 ---- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -+++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -@@ -95,7 +95,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - ++totalChunks; - } - -- this.chunkMap.level.chunkTaskScheduler.lightExecutor.queueRunnable(() -> { // Paper - rewrite chunk system -+ this.chunkMap.level.chunkTaskScheduler.radiusAwareScheduler.queueInfiniteRadiusTask(() -> { // Paper - rewrite chunk system - this.theLightEngine.relightChunks(chunks, (ChunkPos chunkPos) -> { - chunkLightCallback.accept(chunkPos); - Runnable run = () -> { // Folia - region threading diff --git a/patches/server/0010-Make-CraftEntity-getHandle-and-overrides-perform-thr.patch b/patches/server/0006-Make-CraftEntity-getHandle-and-overrides-perform-thr.patch similarity index 98% rename from patches/server/0010-Make-CraftEntity-getHandle-and-overrides-perform-thr.patch rename to patches/server/0006-Make-CraftEntity-getHandle-and-overrides-perform-thr.patch index b96f11b..8e4ca8c 100644 --- a/patches/server/0010-Make-CraftEntity-getHandle-and-overrides-perform-thr.patch +++ b/patches/server/0006-Make-CraftEntity-getHandle-and-overrides-perform-thr.patch @@ -51,10 +51,10 @@ index d9687722e02dfd4088c7030abbf5008eb0a092c8..62484ebf4550b05182f693a3180bbac5 TickThread.ensureTickThread(thisEntity, "May not tick entity scheduler asynchronously"); final List toRun; diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index e8e6743ec078d9dd95d3583ff24cb6791abddf19..885805170e84ce6f26b2789e39c5b5b453c026f0 100644 +index 154ae1492969c4fe9f56ddc190e27337d0396da8..1b7596a8a66c416d8b43495a70b23d6fdeebb245 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2769,6 +2769,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -2889,6 +2889,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { // CraftBukkit start com.google.common.base.Preconditions.checkState(!entity.passengers.contains(this), "Circular entity riding! %s %s", this, entity); @@ -62,7 +62,7 @@ index e8e6743ec078d9dd95d3583ff24cb6791abddf19..885805170e84ce6f26b2789e39c5b5b4 CraftEntity craft = (CraftEntity) entity.getBukkitEntity().getVehicle(); Entity orig = craft == null ? null : craft.getHandle(); if (this.getBukkitEntity() instanceof Vehicle && entity.getBukkitEntity() instanceof LivingEntity) { -@@ -2796,6 +2797,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -2916,6 +2917,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { if (event.isCancelled()) { return false; } @@ -70,7 +70,7 @@ index e8e6743ec078d9dd95d3583ff24cb6791abddf19..885805170e84ce6f26b2789e39c5b5b4 // Spigot end if (this.passengers.isEmpty()) { this.passengers = ImmutableList.of(entity); -@@ -2824,6 +2826,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -2944,6 +2946,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)"); } else { // CraftBukkit start @@ -78,7 +78,7 @@ index e8e6743ec078d9dd95d3583ff24cb6791abddf19..885805170e84ce6f26b2789e39c5b5b4 CraftEntity craft = (CraftEntity) entity.getBukkitEntity().getVehicle(); Entity orig = craft == null ? null : craft.getHandle(); if (this.getBukkitEntity() instanceof Vehicle && entity.getBukkitEntity() instanceof LivingEntity) { -@@ -2851,6 +2854,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -2971,6 +2974,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { if (event.isCancelled()) { return false; } @@ -87,7 +87,7 @@ index e8e6743ec078d9dd95d3583ff24cb6791abddf19..885805170e84ce6f26b2789e39c5b5b4 if (this.passengers.size() == 1 && this.passengers.get(0) == entity) { this.passengers = ImmutableList.of(); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index c43248f5a8e4fe514cfcc1dcf3003baedd950904..0fa6d701f4a16b3a74b55bdd660accb000f06565 100644 +index aa877017799c573a114832fd1809bc80e322ed2c..d68e0ae2e6332a6a99630e2635b22d2cd509377f 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -376,7 +376,7 @@ public final class CraftServer implements Server { @@ -365,7 +365,7 @@ index a82acbbe62bf6aa497e627587e2f3b9be2fbf487..dec03f00091ca781f0eaa4063649883f } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java -index 8f25bb253c2b22e1964afeae705901e926432ef0..ebadeb928d405564d3b029246ac4f2c66958ad9c 100644 +index e405488ba5e0159ff84a72fac1d2da6e9c45238e..631250c15711d0912d2ae34efed02d78d20a0098 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java @@ -9,8 +9,16 @@ public class CraftBat extends CraftAmbient implements Bat { @@ -722,7 +722,7 @@ index 2f7bf9963c3adfc9d2475a86e53a7dcf9f386bfe..e5ffc16d4d5595a7065119c97293f5b4 } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java -index c1db88ceb65eb81c542171fc5465224ef613ce3b..108e0bbe345075a92e133c58df0d1009fc06009d 100644 +index 18623159932df2dd5e43133b4396b43731693780..66f3a67ea0a81d4cfff8365ed055fff6967360b7 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java @@ -10,8 +10,16 @@ public class CraftDolphin extends CraftWaterMob implements Dolphin { @@ -932,7 +932,7 @@ index 75c7645fb5732c43d1da15181cf5c7ee4c3ecd6c..6d3325436a77153438bc40aa86819562 } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index fa762f490fea59e2ec21cfb1c050c4adc30d9998..e4b85cab0d5e0a6d6214e7d57ab2c741439618b4 100644 +index 6a54b582fdab953c452a32e9839e2a916c659c2e..b23e399f65a75e10b6b3aabe8d3ea3660104125b 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -827,7 +827,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { @@ -1023,7 +1023,7 @@ index 84899284703baeb04bfc79251941265d52ac07e8..5b8333e342c639f33acf62e5f8eb72d0 } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java -index 05778023c3b2809c52c148efdfc8677dcc087a7b..92998b85df126725f376941c13dee3dfd407e96a 100644 +index e99314b905a7ed54ceeb156ed92ff1a5793df99a..61bed78df554dc6a9689ea19e159fde990082a1e 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java @@ -15,8 +15,16 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock { @@ -1107,7 +1107,7 @@ index 3c64461119391ec2e987fc936104e21ef0a95ce4..d702e34f65de28df677a9d3616f38b2c } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java -index f4591dfe799538ba8aea104793ceb9995dad0bb6..f74f4932a177ce38dc6339d9b16fca02db481444 100644 +index bac5c30d3aae1b3a7dbfb78f6fd37c11038fc735..20b12701979daf1185332cffdc3895384da30fbb 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java @@ -16,8 +16,16 @@ public class CraftFishHook extends CraftProjectile implements FishHook { @@ -1401,7 +1401,7 @@ index 1f474ef8f9e86da383206bd50ba00c7ed8352c5d..4dd82aef0d4e37336c076f74fe01cbd4 } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index 1b008e5217c5bbf566a213abb92e1c7c43a3a7c2..139f4e7fb1e9fa9b21b0eddbffb05a35d54cf5cf 100644 +index cba6ead3f937f2b3d59c15a864e07e5cb2f2330c..773c07d78877e23f557e448b8d2631534a067350 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java @@ -293,8 +293,16 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { @@ -1527,7 +1527,7 @@ index 9b7b98e21e757ab2caca68de20d0191d0011bc9d..06e2580653db00110b0f0a05cfda9d0e } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java -index 2a297adc49129729751317817d959c2d2566fc3e..7352909a3f2b2f1dc0f96e452869014fa328fc1c 100644 +index 5d6cb013fbc4e5bfe335211b3cc63d2788b5868b..1122ce7763988af38a1f48e789a626d87d8cb4fd 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java @@ -157,8 +157,16 @@ public class CraftItemFrame extends CraftHanging implements ItemFrame { @@ -1611,7 +1611,7 @@ index e515e819774bfb31ec03f05a5502921e66f2b0e2..80cac5d7362577e53ef5ca215ab32618 } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index d43859f8aa7beed82dd3a146bb1086982cd0cda7..3bc0bda187d0702694ad9d98b07aa62f5c44df8b 100644 +index 42a4a45ece562d543cc4fab9d9e7c70573705f10..d065dfacb0980c7997b99ef7ed6e61a85a3c3055 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java @@ -419,8 +419,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { @@ -1999,7 +1999,7 @@ index d7e9b1d7460c0479ff94a2cb52e6c572a464420a..caee1abf4c057afa08be8495bf742f87 } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java -index 9a3734c670972ee91a0d44a1b1fa8493de854a9c..e1ae90699fdc13f6f2bb9ebf227b3803e13e2b88 100644 +index 63e31c237dc3cf37a5e06c1cf3f030c9f1df3d38..8c02dabbfee88ee6c3315c8174da7236eecdd8cc 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java @@ -10,8 +10,16 @@ public class CraftPhantom extends CraftFlying implements Phantom, CraftEnemy { @@ -2146,7 +2146,7 @@ index beea227855f0b978e655efc298024120df8f4945..e1b7922ed298b6b3068c3f5fbe3b4030 } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index b0d9ab8088ee8b894fbee4c4b88bf081a419cb1f..a381e6a83762af08ed2005383d188313c12612ff 100644 +index 93e0c4fd626455db1e14785a0b6750ba07fa0fe0..bc59a9d2f965c8e8638a2eeb3fcf661ed81f1727 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -593,7 +593,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @@ -2158,7 +2158,7 @@ index b0d9ab8088ee8b894fbee4c4b88bf081a419cb1f..a381e6a83762af08ed2005383d188313 final ServerGamePacketListenerImpl connection = this.getHandle().connection; if (connection != null) { connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message, cause); -@@ -2017,9 +2017,16 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2023,9 +2023,16 @@ public class CraftPlayer extends CraftHumanEntity implements Player { return this; } @@ -2176,11 +2176,11 @@ index b0d9ab8088ee8b894fbee4c4b88bf081a419cb1f..a381e6a83762af08ed2005383d188313 } public void setHandle(final ServerPlayer entity) { -@@ -3028,7 +3035,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -3024,7 +3031,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { { if ( CraftPlayer.this.getHealth() <= 0 && CraftPlayer.this.isOnline() ) { -- server.getServer().getPlayerList().respawn( CraftPlayer.this.getHandle(), false, RespawnReason.PLUGIN ); +- server.getServer().getPlayerList().respawn( CraftPlayer.this.getHandle(), false, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.PLUGIN ); + CraftPlayer.this.getHandle().respawn(null); // Folia - region threading } } @@ -2271,7 +2271,7 @@ index 3cb4860fea30bfaf2147b4f29a34336b6e417d6c..5d852e0a34004b877555157dd45020e5 } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java -index ad10ba1dbaf81e767441aa8e1babed1d3e654121..55113e60883e2d74f46ffd2b7d273a517baa7584 100644 +index f0b061979f9acdce6d06f70b651692c841418d96..6fb31c15d65885614b3878b5bcce08567d5b2089 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRaider.java @@ -14,8 +14,16 @@ public abstract class CraftRaider extends CraftMonster implements Raider { @@ -2523,7 +2523,7 @@ index d8b4df1300791aaf310465ec1577b1b8c202901a..17b83eb8563586f1ddf252f438d52d55 } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSniffer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSniffer.java -index d9b40521e0597f4e2236a34c25cc4625552be66f..1517b557a62fe5f68217d343953ac0cb44531e01 100644 +index ca3bffd07e0e8f3b2409917cf561d4755c8fba21..79de600fbc424d80b5f37a60ad60a8f18e746ff6 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSniffer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSniffer.java @@ -17,8 +17,16 @@ public class CraftSniffer extends CraftAnimals implements Sniffer { @@ -2923,7 +2923,7 @@ index 4352af0a76ce4a4cd4afbea96f4851ef2b64ac7d..f8aa5bc3d846d2fd785c612dd3906b84 } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java -index a14d0a688b9054988b5c86c94738e4aaca9f9cfd..dd5b95c809e2bbdac17981af0010f55403713c87 100644 +index d3546fb082a56b4ce077ded5d25909e15f7eb593..319efb68aebd6d1182dede7b2b6f368b3d7b62c2 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTurtle.java @@ -10,8 +10,16 @@ public class CraftTurtle extends CraftAnimals implements Turtle { @@ -2965,7 +2965,7 @@ index bcfca66c7b99b9d514fe850d6cc6abd7e36ccaf7..90c295ff95bbee4f51fda1be2f58312d } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -index 4e880409b06086568627f3e930159f1abb979984..db774dac486fd0a4b49a45c8248b594f877b141e 100644 +index e986767316a717bdbdff7a9ccaaeba068ab2a6d8..ca6a6507cadc6c3ada1a9d8ce35cd48d295e2884 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java @@ -31,8 +31,16 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { @@ -3028,7 +3028,7 @@ index bea22e002a9d41b0e364eff1109d5a67c9824a00..fc7a87ed98801914d5cf3e6784f0025d } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java -index ecf0c4a7d1ce2b254d91b3276fa24c149329737a..fadc797d3c298c6ad4b2e6abc70b59e5ffc58e92 100644 +index f9a3d060ff4da6047d11f2b024d144572c513399..824e2c7aa9263195cb173da2b5612b3b8bc3a046 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java @@ -10,8 +10,16 @@ public class CraftWanderingTrader extends CraftAbstractVillager implements Wande @@ -3112,7 +3112,7 @@ index 9039db1a72009342063d4db08e18e6aee18836e8..c2bceaeabf13d37506eea540cb153d10 } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -index 1a21d30620f13a48976da5ead7edab201ea68b21..6eb758bed0d0e5394adfee3294aec2bf53daf3ed 100644 +index 1477c2c04d8f5c5639ce94808fe2a7029cedaeb2..a6dc41a1eac7aa08a4e13489a7dabb61cd4eadd7 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java @@ -22,8 +22,16 @@ public class CraftWither extends CraftMonster implements Wither, com.destroystok diff --git a/patches/server/0007-Cache-whether-region-files-do-not-exist.patch b/patches/server/0007-Cache-whether-region-files-do-not-exist.patch deleted file mode 100644 index 29960d1..0000000 --- a/patches/server/0007-Cache-whether-region-files-do-not-exist.patch +++ /dev/null @@ -1,119 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Thu, 2 Mar 2023 23:19:04 -0800 -Subject: [PATCH] Cache whether region files do not exist - -The repeated I/O of creating the directory for the regionfile -or for checking if the file exists can be heavy in -when pushing chunk generation extremely hard - as each chunk gen -request may effectively go through to the I/O thread. - -diff --git a/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java b/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java -index a08cde4eefe879adcee7c4118bc38f98c5097ed0..8a11e10b01fa012b2f98b1c193c53251e848f909 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java -+++ b/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java -@@ -819,8 +819,14 @@ public final class RegionFileIOThread extends PrioritisedQueueExecutorThread { - return file.hasChunk(chunkPos) ? Boolean.TRUE : Boolean.FALSE; - }); - } else { -+ // first check if the region file for sure does not exist -+ if (taskController.doesRegionFileNotExist(chunkX, chunkZ)) { -+ return Boolean.FALSE; -+ } // else: it either exists or is not known, fall back to checking the loaded region file -+ - return taskController.computeForRegionFileIfLoaded(chunkX, chunkZ, (final RegionFile file) -> { - if (file == null) { // null if not loaded -+ // not sure at this point, let the I/O thread figure it out - return Boolean.TRUE; - } - -@@ -1116,6 +1122,10 @@ public final class RegionFileIOThread extends PrioritisedQueueExecutorThread { - return !this.tasks.isEmpty(); - } - -+ public boolean doesRegionFileNotExist(final int chunkX, final int chunkZ) { -+ return this.getCache().doesRegionFileNotExistNoIO(new ChunkPos(chunkX, chunkZ)); -+ } -+ - public T computeForRegionFile(final int chunkX, final int chunkZ, final boolean existingOnly, final Function function) { - final RegionFileStorage cache = this.getCache(); - final RegionFile regionFile; -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -index bd502ca721de0cab438d995efa00ad0554c0d2fe..9633b01d2d961fd1403e353484d336376ef009eb 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -@@ -28,6 +28,35 @@ public class RegionFileStorage implements AutoCloseable { - - private final boolean isChunkData; // Paper - -+ // Paper start - cache regionfile does not exist state -+ static final int MAX_NON_EXISTING_CACHE = 1024 * 64; -+ private final it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet nonExistingRegionFiles = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet(); -+ private synchronized boolean doesRegionFilePossiblyExist(long position) { -+ if (this.nonExistingRegionFiles.contains(position)) { -+ this.nonExistingRegionFiles.addAndMoveToFirst(position); -+ return false; -+ } -+ return true; -+ } -+ -+ private synchronized void createRegionFile(long position) { -+ this.nonExistingRegionFiles.remove(position); -+ } -+ -+ private synchronized void markNonExisting(long position) { -+ if (this.nonExistingRegionFiles.addAndMoveToFirst(position)) { -+ while (this.nonExistingRegionFiles.size() >= MAX_NON_EXISTING_CACHE) { -+ this.nonExistingRegionFiles.removeLastLong(); -+ } -+ } -+ } -+ -+ public synchronized boolean doesRegionFileNotExistNoIO(ChunkPos pos) { -+ long key = ChunkPos.asLong(pos.getRegionX(), pos.getRegionZ()); -+ return !this.doesRegionFilePossiblyExist(key); -+ } -+ // Paper end - cache regionfile does not exist state -+ - protected RegionFileStorage(Path directory, boolean dsync) { // Paper - protected constructor - // Paper start - add isChunkData param - this(directory, dsync, false); -@@ -77,7 +106,7 @@ public class RegionFileStorage implements AutoCloseable { - } - public synchronized RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly, boolean lock) throws IOException { - // Paper end -- long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); -+ long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()); final long regionPos = i; // Paper - OBFHELPER - RegionFile regionfile = (RegionFile) this.regionCache.getAndMoveToFirst(i); - - if (regionfile != null) { -@@ -89,15 +118,27 @@ public class RegionFileStorage implements AutoCloseable { - // Paper end - return regionfile; - } else { -+ // Paper start - cache regionfile does not exist state -+ if (existingOnly && !this.doesRegionFilePossiblyExist(regionPos)) { -+ return null; -+ } -+ // Paper end - cache regionfile does not exist state - if (this.regionCache.size() >= io.papermc.paper.configuration.GlobalConfiguration.get().misc.regionFileCacheSize) { // Paper - configurable - ((RegionFile) this.regionCache.removeLast()).close(); - } - -- FileUtil.createDirectoriesSafe(this.folder); -+ // Paper - only create directory if not existing only - moved down - Path path = this.folder; - int j = chunkcoordintpair.getRegionX(); - Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); // Paper - diff on change -- if (existingOnly && !java.nio.file.Files.exists(path1)) return null; // CraftBukkit -+ if (existingOnly && !java.nio.file.Files.exists(path1)) { // Paper start - cache regionfile does not exist state -+ this.markNonExisting(regionPos); -+ return null; // CraftBukkit -+ } else { -+ this.createRegionFile(regionPos); -+ } -+ // Paper end - cache regionfile does not exist state -+ FileUtil.createDirectoriesSafe(this.folder); // Paper - only create directory if not existing only - moved from above - RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync, this.isChunkData); // Paper - allow for chunk regionfiles to regen header - - this.regionCache.putAndMoveToFirst(i, regionfile1); diff --git a/patches/server/0011-Disable-mid-tick-task-execution.patch b/patches/server/0007-Disable-mid-tick-task-execution.patch similarity index 88% rename from patches/server/0011-Disable-mid-tick-task-execution.patch rename to patches/server/0007-Disable-mid-tick-task-execution.patch index baad64c..ed5b031 100644 --- a/patches/server/0011-Disable-mid-tick-task-execution.patch +++ b/patches/server/0007-Disable-mid-tick-task-execution.patch @@ -10,10 +10,10 @@ the impact from scaling the region threads, but is not a fix to the underlying issue. diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 3478d9c1db9acf19165df7308b6ae4461fa8fef7..61bac6fda2d2f4b3db8a3f7e3003f47c84d5c4cd 100644 +index 37121e7be9ed0056b50b8d01dff84ee8660bab47..32562030474e935b1da359b94028a10e39f1a5a4 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -2884,6 +2884,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop -Date: Sat, 1 Apr 2023 00:34:29 -0700 -Subject: [PATCH] Acquire scheduling lock in - NewChunkHolder#onFullChunkLoadChange - -It modifies data that should be held by the lock: pending -chunk status - -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java -index 3b70ccd8e0b1ada943f57faf99c23b2935249cf6..12feb739a784a0108256451a37d94d041b7a5cdc 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java -@@ -1203,8 +1203,12 @@ public final class NewChunkHolder { - } - } - -- // only call on main thread, must hold ticket level and scheduling lock -+ // only call on main thread // Folia - update comment - private void onFullChunkLoadChange(final boolean loaded, final List changedFullStatus) { -+ // Folia start - chunk system fix - acquire scheduling lock -+ this.scheduler.schedulingLock.lock(); -+ try { -+ // Folia end - chunk system fix - acquire scheduling lock - for (int dz = -NEIGHBOUR_RADIUS; dz <= NEIGHBOUR_RADIUS; ++dz) { - for (int dx = -NEIGHBOUR_RADIUS; dx <= NEIGHBOUR_RADIUS; ++dx) { - final NewChunkHolder holder = (dx | dz) == 0 ? this : this.scheduler.chunkHolderManager.getChunkHolder(dx + this.chunkX, dz + this.chunkZ); -@@ -1219,6 +1223,11 @@ public final class NewChunkHolder { - } - } - } -+ // Folia start - chunk system fix - acquire scheduling lock -+ } finally { -+ this.scheduler.schedulingLock.unlock(); -+ } -+ // Folia end - chunk system fix - acquire scheduling lock - } - - private ChunkHolder.FullChunkStatus updateCurrentState(final ChunkHolder.FullChunkStatus to) { diff --git a/patches/server/0021-Do-not-access-POI-data-for-lodestone-compass.patch b/patches/server/0016-Do-not-access-POI-data-for-lodestone-compass.patch similarity index 100% rename from patches/server/0021-Do-not-access-POI-data-for-lodestone-compass.patch rename to patches/server/0016-Do-not-access-POI-data-for-lodestone-compass.patch diff --git a/patches/server/0017-Use-coordinate-based-locking-to-increase-chunk-syste.patch b/patches/server/0017-Use-coordinate-based-locking-to-increase-chunk-syste.patch new file mode 100644 index 0000000..99352ec --- /dev/null +++ b/patches/server/0017-Use-coordinate-based-locking-to-increase-chunk-syste.patch @@ -0,0 +1,482 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Fri, 12 May 2023 20:37:56 -0700 +Subject: [PATCH] Use coordinate-based locking to increase chunk system + parallelism + +A significant overhead in Folia comes from the chunk system's +locks, the ticket lock and the scheduling lock. The public +test server, which had ~330 players, had signficant performance +problems with these locks: ~80% of the time spent ticking +was _waiting_ for the locks to free. Given that it used +around 15 cores total at peak, this is a complete and utter loss +of potential. + +To address this issue, I have replaced the ticket lock and scheduling +lock with two ReentrantAreaLocks. The ReentrantAreaLock takes a +shift, which is used internally to group positions into sections. +This grouping is neccessary, as the possible radius of area that +needs to be acquired for any given lock usage is up to 64. As such, +the shift is critical to reduce the number of areas required to lock +for any lock operation. Currently, it is set to a shift of 6, which +is identical to the ticket level propagation shift (and, it must be +at least the ticket level propagation shift AND the region shift). + +The chunk system locking changes required a complete rewrite of the +chunk system tick, chunk system unload, and chunk system ticket level +propagation - as all of the previous logic only works with a single +global lock. + +This does introduce two other section shifts: the lock shift, and the +ticket shift. The lock shift is simply what shift the area locks use, +and the ticket shift represents the size of the ticket sections. +Currently, these values are just set to the region shift for simplicity. +However, they are not arbitrary: the lock shift must be at least the size +of the ticket shift and must be at least the size of the region shift. +The ticket shift must also be >= the ceil(log2(max ticket level source)). + +The chunk system's ticket propagator is now global state, instead of +region state. This cleans up the logic for ticket levels significantly, +and removes usage of the region lock in this area, but it also means +that the addition of a ticket no longer creates a region. To alleviate +the side effects of this change, the global tick thread now processes +ticket level updates for each world every tick to guarantee eventual +ticket level processing. The chunk system also provides a hook to +process ticket level changes in a given _section_, so that the +region queue can guarantee that after adding its reference counter +that the region section is created/exists/wont be destroyed. + +The ticket propagator operates by updating the sources in a single ticket +section, and propagating the updates to its 1 radius neighbours. This +allows the ticket updates to occur in parallel or selectively (see above). +Currently, the process ticket level update function operates by +polling from a concurrent queue of sections to update and simply +invoking the single section update logic. This allows the function +to operate completely in parallel, provided the queue is ordered right. +Additionally, this limits the area used in the ticket/scheduling lock +when processing updates, which should massively increase parallelism compared +to before. + +The chunk system ticket addition for expirable ticket types has been modified +to no longer track exact tick deadlines, as this relies on what region the +ticket is in. Instead, the chunk system tracks a map of +lock section -> (chunk coordinate -> expire ticket count) and every ticket +has been changed to have a removeDelay count that is decremented each tick. +Each region searches its own sections to find tickets to try to expire. + +Chunk system unloading has been modified to track unloads by lock section. +The ordering is determined by which section a chunk resides in. +The unload process now removes from unload sections and processes +the full unload stages (1, 2, 3) before moving to the next section, if possible. +This allows the unload logic to only hold one lock section at a time for +each lock, which is a massive parallelism increase. + +In stress testing, these changes lowered the locking overhead to only 5% +from ~70%, which completely fix the original problem as described. + +diff --git a/src/main/java/ca/spottedleaf/concurrentutil/lock/AreaLock.java b/src/main/java/ca/spottedleaf/concurrentutil/lock/AreaLock.java +deleted file mode 100644 +index 6a155b779914828a0d4199bdfcb0d6fca25e1581..0000000000000000000000000000000000000000 +--- a/src/main/java/ca/spottedleaf/concurrentutil/lock/AreaLock.java ++++ /dev/null +@@ -1,146 +0,0 @@ +-package ca.spottedleaf.concurrentutil.lock; +- +-import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; +-import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +-import java.util.ArrayList; +-import java.util.List; +-import java.util.concurrent.locks.LockSupport; +- +-public final class AreaLock { +- +- private final int coordinateShift; +- +- private final Long2ReferenceOpenHashMap nodesByPosition = new Long2ReferenceOpenHashMap<>(1024, 0.10f); +- +- public AreaLock(final int coordinateShift) { +- this.coordinateShift = coordinateShift; +- } +- +- private static long key(final int x, final int z) { +- return ((long)z << 32) | (x & 0xFFFFFFFFL); +- } +- +- public Node lock(final int x, final int z, final int radius) { +- final Thread thread = Thread.currentThread(); +- final int minX = (x - radius) >> this.coordinateShift; +- final int minZ = (z - radius) >> this.coordinateShift; +- final int maxX = (x + radius) >> this.coordinateShift; +- final int maxZ = (z + radius) >> this.coordinateShift; +- +- final Node node = new Node(x, z, radius, thread); +- +- synchronized (this) { +- ReferenceOpenHashSet parents = null; +- for (int currZ = minZ; currZ <= maxZ; ++currZ) { +- for (int currX = minX; currX <= maxX; ++currX) { +- final Node dependency = this.nodesByPosition.put(key(currX, currZ), node); +- if (dependency == null) { +- continue; +- } +- +- if (parents == null) { +- parents = new ReferenceOpenHashSet<>(); +- } +- +- if (parents.add(dependency)) { +- // added a dependency, so we need to add as a child to the dependency +- if (dependency.children == null) { +- dependency.children = new ArrayList<>(); +- } +- dependency.children.add(node); +- } +- } +- } +- +- if (parents == null) { +- // no dependencies, so we can just return immediately +- return node; +- } // else: we need to lock +- +- node.parents = parents; +- } +- +- while (!node.unlocked) { +- LockSupport.park(node); +- } +- +- return node; +- } +- +- public void unlock(final Node node) { +- List toUnpark = null; +- +- final int x = node.x; +- final int z = node.z; +- final int radius = node.radius; +- +- final int minX = (x - radius) >> this.coordinateShift; +- final int minZ = (z - radius) >> this.coordinateShift; +- final int maxX = (x + radius) >> this.coordinateShift; +- final int maxZ = (z + radius) >> this.coordinateShift; +- +- synchronized (this) { +- final List children = node.children; +- if (children != null) { +- // try to unlock children +- for (int i = 0, len = children.size(); i < len; ++i) { +- final Node child = children.get(i); +- if (!child.parents.remove(node)) { +- throw new IllegalStateException(); +- } +- if (child.parents.isEmpty()) { +- // we can unlock, as it now has no dependencies in front +- child.parents = null; +- if (toUnpark == null) { +- toUnpark = new ArrayList<>(); +- toUnpark.add(child); +- } else { +- toUnpark.add(child); +- } +- } +- } +- } +- +- // remove node from dependency map +- for (int currZ = minZ; currZ <= maxZ; ++currZ) { +- for (int currX = minX; currX <= maxX; ++currX) { +- // node: we only remove if we match, as a mismatch indicates a child node which of course has not +- // yet been unlocked +- this.nodesByPosition.remove(key(currX, currZ), node); +- } +- } +- } +- +- if (toUnpark == null) { +- return; +- } +- +- // we move the unpark / unlock logic here because we want to avoid performing work while holding the lock +- +- for (int i = 0, len = toUnpark.size(); i < len; ++i) { +- final Node toUnlock = toUnpark.get(i); +- toUnlock.unlocked = true; // must be volatile and before unpark() +- LockSupport.unpark(toUnlock.thread); +- } +- } +- +- public static final class Node { +- +- public final int x; +- public final int z; +- public final int radius; +- public final Thread thread; +- +- private List children; +- private ReferenceOpenHashSet parents; +- +- private volatile boolean unlocked; +- +- public Node(final int x, final int z, final int radius, final Thread thread) { +- this.x = x; +- this.z = z; +- this.radius = radius; +- this.thread = thread; +- } +- } +-} +diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java +index 8a5e3138c0f5e3f275c7352faf07bf39c04d00ca..daeed35877ffd0c110b2ec259030b038cf60cefb 100644 +--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java ++++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java +@@ -191,7 +191,7 @@ public final class ChunkTaskScheduler { + // it must be >= ticket propagator section shift so that the ticket propagator can assume that owning a position implies owning + // the entire section + // we just take the max, as we want the smallest shift that satifies these properties +- private static final int LOCK_SHIFT = ThreadedTicketLevelPropagator.SECTION_SHIFT; ++ private static final int LOCK_SHIFT = Math.max(io.papermc.paper.threadedregions.ThreadedTicketLevelPropagator.SECTION_SHIFT, io.papermc.paper.threadedregions.TickRegions.getRegionChunkShift()); // Folia - region threading + public static int getChunkSystemLockShift() { + return LOCK_SHIFT; + } +diff --git a/src/main/java/io/papermc/paper/threadedregions/RegionizedServer.java b/src/main/java/io/papermc/paper/threadedregions/RegionizedServer.java +index 6c1d55144f044f39926ddf998104950b9efe3ee1..8e31c6ee9ee16aff699e124a9b0554eaafa5c1ac 100644 +--- a/src/main/java/io/papermc/paper/threadedregions/RegionizedServer.java ++++ b/src/main/java/io/papermc/paper/threadedregions/RegionizedServer.java +@@ -185,7 +185,96 @@ public final class RegionizedServer { + private long lastServerStatus; + private long tickCount; + ++ /* ++ private final java.util.Random random = new java.util.Random(4L); ++ private final List> walkers = ++ new java.util.ArrayList<>(); ++ static final int PLAYERS = 100; ++ static final int RAD_BLOCKS = 10000; ++ static final int RAD = RAD_BLOCKS >> 4; ++ static final int RAD_BIG_BLOCKS = 100_000; ++ static final int RAD_BIG = RAD_BIG_BLOCKS >> 4; ++ static final int VD = 4; ++ static final int BIG_PLAYERS = 50; ++ static final double WALK_CHANCE = 0.10; ++ static final double TP_CHANCE = 0.01; ++ ++ private ServerLevel getWorld() { ++ return this.worlds.get(0); ++ } ++ ++ private void init2() { ++ for (int i = 0; i < PLAYERS; ++i) { ++ int rad = i < BIG_PLAYERS ? RAD_BIG : RAD; ++ int posX = this.random.nextInt(-rad, rad + 1); ++ int posZ = this.random.nextInt(-rad, rad + 1); ++ ++ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap<>(null) { ++ @Override ++ protected void addCallback(Void parameter, int chunkX, int chunkZ) { ++ ServerLevel world = RegionizedServer.this.getWorld(); ++ world.chunkTaskScheduler.chunkHolderManager.addTicketAtLevel( ++ net.minecraft.server.level.TicketType.PLAYER, chunkX, chunkZ, io.papermc.paper.chunk.system.scheduling.ChunkHolderManager.ENTITY_TICKING_TICKET_LEVEL, new net.minecraft.world.level.ChunkPos(posX, posZ) ++ ); ++ } ++ ++ @Override ++ protected void removeCallback(Void parameter, int chunkX, int chunkZ) { ++ ServerLevel world = RegionizedServer.this.getWorld(); ++ world.chunkTaskScheduler.chunkHolderManager.removeTicketAtLevel( ++ net.minecraft.server.level.TicketType.PLAYER, chunkX, chunkZ, io.papermc.paper.chunk.system.scheduling.ChunkHolderManager.ENTITY_TICKING_TICKET_LEVEL, new net.minecraft.world.level.ChunkPos(posX, posZ) ++ ); ++ } ++ }; ++ ++ map.add(posX, posZ, VD); ++ ++ walkers.add(map); ++ } ++ } ++ ++ private void randomWalk() { ++ if (this.walkers.isEmpty()) { ++ this.init2(); ++ return; ++ } ++ ++ for (int i = 0; i < PLAYERS; ++i) { ++ if (this.random.nextDouble() > WALK_CHANCE) { ++ continue; ++ } ++ ++ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = this.walkers.get(i); ++ ++ int updateX = this.random.nextInt(-1, 2); ++ int updateZ = this.random.nextInt(-1, 2); ++ ++ map.update(map.lastChunkX + updateX, map.lastChunkZ + updateZ, VD); ++ } ++ ++ for (int i = 0; i < PLAYERS; ++i) { ++ if (random.nextDouble() >= TP_CHANCE) { ++ continue; ++ } ++ ++ int rad = i < BIG_PLAYERS ? RAD_BIG : RAD; ++ int posX = random.nextInt(-rad, rad + 1); ++ int posZ = random.nextInt(-rad, rad + 1); ++ ++ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = walkers.get(i); ++ ++ map.update(posX, posZ, VD); ++ } ++ } ++ */ ++ + private void globalTick(final int tickCount) { ++ /* ++ if (false) { ++ io.papermc.paper.threadedregions.ThreadedTicketLevelPropagator.main(null); ++ } ++ this.randomWalk(); ++ */ + ++this.tickCount; + // expire invalid click command callbacks + io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue((int)this.tickCount); +@@ -311,6 +400,8 @@ public final class RegionizedServer { + this.tickTime(world, tickCount); + + world.updateTickData(); ++ ++ world.chunkTaskScheduler.chunkHolderManager.processTicketUpdates(); // Folia - use area based lock to reduce contention - required now to eventually process ticket updates + } + + private void updateRaids(final ServerLevel world) { +diff --git a/src/main/java/io/papermc/paper/threadedregions/RegionizedTaskQueue.java b/src/main/java/io/papermc/paper/threadedregions/RegionizedTaskQueue.java +index 4a095e69584d7dbbefafe6e0a4a1a1090172ac9e..2e4514e5a45db6e625ef7799b63a9285a3bc1030 100644 +--- a/src/main/java/io/papermc/paper/threadedregions/RegionizedTaskQueue.java ++++ b/src/main/java/io/papermc/paper/threadedregions/RegionizedTaskQueue.java +@@ -69,7 +69,7 @@ public final class RegionizedTaskQueue { + public static final class WorldRegionTaskData { + private final ServerLevel world; + private final MultiThreadedQueue globalChunkTask = new MultiThreadedQueue<>(); +- private final SWMRLong2ObjectHashTable referenceCounters = new SWMRLong2ObjectHashTable<>(); ++ private final java.util.concurrent.ConcurrentHashMap referenceCounters = new java.util.concurrent.ConcurrentHashMap<>(); // Folia - use area based lock to reduce contention + + public WorldRegionTaskData(final ServerLevel world) { + this.world = world; +@@ -115,17 +115,25 @@ public final class RegionizedTaskQueue { + ); + } + ++ // Folia start - use area based lock to reduce contention ++ private void processTicketUpdates(final long coord) { ++ this.world.chunkTaskScheduler.chunkHolderManager.processTicketUpdates(CoordinateUtils.getChunkX(coord), CoordinateUtils.getChunkZ(coord)); ++ } ++ // Folia end - use area based lock to reduce contention ++ + private void decrementReference(final AtomicLong reference, final long coord) { + final long val = reference.decrementAndGet(); + if (val == 0L) { +- final ReentrantLock ticketLock = this.world.chunkTaskScheduler.chunkHolderManager.ticketLock; +- ticketLock.lock(); ++ final int chunkX = CoordinateUtils.getChunkX(coord); // Folia - use area based lock to reduce contention ++ final int chunkZ = CoordinateUtils.getChunkZ(coord); // Folia - use area based lock to reduce contention ++ final io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkCoordinate key = new io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkCoordinate(coord); // Folia - use area based lock to reduce contention ++ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.world.chunkTaskScheduler.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ); // Folia - use area based lock to reduce contention + try { +- if (this.referenceCounters.remove(coord, reference)) { ++ if (this.referenceCounters.remove(key, reference)) { // Folia - use area based lock to reduce contention + WorldRegionTaskData.this.removeTicket(coord); + } // else: race condition, something replaced our reference - not our issue anymore + } finally { +- ticketLock.unlock(); ++ this.world.chunkTaskScheduler.chunkHolderManager.ticketLockArea.unlock(ticketLock); // Folia - use area based lock to reduce contention + } + } else if (val < 0L) { + throw new IllegalStateException("Reference count < 0: " + val); +@@ -133,7 +141,8 @@ public final class RegionizedTaskQueue { + } + + private AtomicLong incrementReference(final long coord) { +- final AtomicLong ret = this.referenceCounters.get(coord); ++ final io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkCoordinate key = new io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkCoordinate(coord); // Folia - use area based lock to reduce contention ++ final AtomicLong ret = this.referenceCounters.get(key); // Folia - use area based lock to reduce contention + if (ret != null) { + // try to fast acquire counter + int failures = 0; +@@ -156,41 +165,54 @@ public final class RegionizedTaskQueue { + } + + // slow acquire +- final ReentrantLock ticketLock = this.world.chunkTaskScheduler.chunkHolderManager.ticketLock; +- ticketLock.lock(); ++ final int chunkX = CoordinateUtils.getChunkX(coord); // Folia - use area based lock to reduce contention ++ final int chunkZ = CoordinateUtils.getChunkZ(coord); // Folia - use area based lock to reduce contention ++ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.world.chunkTaskScheduler.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ); // Folia - use area based lock to reduce contention ++ final AtomicLong ret2; ++ final boolean processTicketUpdates; + try { + final AtomicLong replace = new AtomicLong(1L); +- final AtomicLong valueInMap = this.referenceCounters.putIfAbsent(coord, replace); ++ final AtomicLong valueInMap = this.referenceCounters.putIfAbsent(key, replace); // Folia - use area based lock to reduce contention + if (valueInMap == null) { + // replaced, we should usually be here + this.addTicket(coord); +- return replace; +- } // else: need to attempt to acquire the reference ++ ret2 = replace; ++ processTicketUpdates = true; ++ } else { ++ processTicketUpdates = false; ++ int failures = 0; ++ for (long curr = valueInMap.get();;) { ++ if (curr == 0L) { ++ // don't need to add ticket here, since ticket is only removed during the lock ++ // we just need to replace the value in the map so that the thread removing fails and doesn't ++ // remove the ticket (see decrementReference) ++ this.referenceCounters.put(key, replace); // Folia - use area based lock to reduce contention ++ ret2 = replace; ++ break; ++ } + +- int failures = 0; +- for (long curr = valueInMap.get();;) { +- if (curr == 0L) { +- // don't need to add ticket here, since ticket is only removed during the lock +- // we just need to replace the value in the map so that the thread removing fails and doesn't +- // remove the ticket (see decrementReference) +- this.referenceCounters.put(coord, replace); +- return replace; +- } ++ for (int i = 0; i < failures; ++i) { ++ ConcurrentUtil.backoff(); ++ } + +- for (int i = 0; i < failures; ++i) { +- ConcurrentUtil.backoff(); +- } ++ if (curr == (curr = valueInMap.compareAndExchange(curr, curr + 1L))) { ++ // acquired ++ ret2 = valueInMap; ++ break; ++ } + +- if (curr == (curr = valueInMap.compareAndExchange(curr, curr + 1L))) { +- // acquired +- return valueInMap; ++ ++failures; + } +- +- ++failures; + } + } finally { +- ticketLock.unlock(); ++ this.world.chunkTaskScheduler.chunkHolderManager.ticketLockArea.unlock(ticketLock); // Folia - use area based lock to reduce contention ++ } ++ ++ if (processTicketUpdates) { ++ this.processTicketUpdates(coord); + } ++ ++ return ret2; + } + } + diff --git a/patches/server/0023-Synchronize-PaperPermissionManager.patch b/patches/server/0018-Synchronize-PaperPermissionManager.patch similarity index 100% rename from patches/server/0023-Synchronize-PaperPermissionManager.patch rename to patches/server/0018-Synchronize-PaperPermissionManager.patch diff --git a/patches/server/0027-Fix-off-region-raid-heroes.patch b/patches/server/0019-Fix-off-region-raid-heroes.patch similarity index 96% rename from patches/server/0027-Fix-off-region-raid-heroes.patch rename to patches/server/0019-Fix-off-region-raid-heroes.patch index 16f8074..186e3d3 100644 --- a/patches/server/0027-Fix-off-region-raid-heroes.patch +++ b/patches/server/0019-Fix-off-region-raid-heroes.patch @@ -9,7 +9,7 @@ raid before it's completion, it would throw an exception due to not being on the same region thread anymore. diff --git a/src/main/java/net/minecraft/world/entity/raid/Raid.java b/src/main/java/net/minecraft/world/entity/raid/Raid.java -index 359f1690497eac00899eb26c17308e0a6fe943ad..2289de057dd3c09cbe76f114e4bcc78b737bb6a3 100644 +index 113583d0d9de744e314bc7ee15cb8e21ec4a92f9..ddec5251b335a25cb9d8726396d90d94572dbd6b 100644 --- a/src/main/java/net/minecraft/world/entity/raid/Raid.java +++ b/src/main/java/net/minecraft/world/entity/raid/Raid.java @@ -407,12 +407,25 @@ public class Raid { diff --git a/patches/server/0022-Use-coordinate-based-locking-to-increase-chunk-syste.patch b/patches/server/0022-Use-coordinate-based-locking-to-increase-chunk-syste.patch deleted file mode 100644 index 6d41323..0000000 --- a/patches/server/0022-Use-coordinate-based-locking-to-increase-chunk-syste.patch +++ /dev/null @@ -1,5040 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Fri, 12 May 2023 20:37:56 -0700 -Subject: [PATCH] Use coordinate-based locking to increase chunk system - parallelism - -A significant overhead in Folia comes from the chunk system's -locks, the ticket lock and the scheduling lock. The public -test server, which had ~330 players, had signficant performance -problems with these locks: ~80% of the time spent ticking -was _waiting_ for the locks to free. Given that it used -around 15 cores total at peak, this is a complete and utter loss -of potential. - -To address this issue, I have replaced the ticket lock and scheduling -lock with two ReentrantAreaLocks. The ReentrantAreaLock takes a -shift, which is used internally to group positions into sections. -This grouping is neccessary, as the possible radius of area that -needs to be acquired for any given lock usage is up to 64. As such, -the shift is critical to reduce the number of areas required to lock -for any lock operation. Currently, it is set to a shift of 6, which -is identical to the ticket level propagation shift (and, it must be -at least the ticket level propagation shift AND the region shift). - -The chunk system locking changes required a complete rewrite of the -chunk system tick, chunk system unload, and chunk system ticket level -propagation - as all of the previous logic only works with a single -global lock. - -This does introduce two other section shifts: the lock shift, and the -ticket shift. The lock shift is simply what shift the area locks use, -and the ticket shift represents the size of the ticket sections. -Currently, these values are just set to the region shift for simplicity. -However, they are not arbitrary: the lock shift must be at least the size -of the ticket shift and must be at least the size of the region shift. -The ticket shift must also be >= the ceil(log2(max ticket level source)). - -The chunk system's ticket propagator is now global state, instead of -region state. This cleans up the logic for ticket levels significantly, -and removes usage of the region lock in this area, but it also means -that the addition of a ticket no longer creates a region. To alleviate -the side effects of this change, the global tick thread now processes -ticket level updates for each world every tick to guarantee eventual -ticket level processing. The chunk system also provides a hook to -process ticket level changes in a given _section_, so that the -region queue can guarantee that after adding its reference counter -that the region section is created/exists/wont be destroyed. - -The ticket propagator operates by updating the sources in a single ticket -section, and propagating the updates to its 1 radius neighbours. This -allows the ticket updates to occur in parallel or selectively (see above). -Currently, the process ticket level update function operates by -polling from a concurrent queue of sections to update and simply -invoking the single section update logic. This allows the function -to operate completely in parallel, provided the queue is ordered right. -Additionally, this limits the area used in the ticket/scheduling lock -when processing updates, which should massively increase parallelism compared -to before. - -The chunk system ticket addition for expirable ticket types has been modified -to no longer track exact tick deadlines, as this relies on what region the -ticket is in. Instead, the chunk system tracks a map of -lock section -> (chunk coordinate -> expire ticket count) and every ticket -has been changed to have a removeDelay count that is decremented each tick. -Each region searches its own sections to find tickets to try to expire. - -Chunk system unloading has been modified to track unloads by lock section. -The ordering is determined by which section a chunk resides in. -The unload process now removes from unload sections and processes -the full unload stages (1, 2, 3) before moving to the next section, if possible. -This allows the unload logic to only hold one lock section at a time for -each lock, which is a massive parallelism increase. - -In stress testing, these changes lowered the locking overhead to only 5% -from ~70%, which completely fix the original problem as described. - -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/lock/AreaLock.java b/src/main/java/ca/spottedleaf/concurrentutil/lock/AreaLock.java -deleted file mode 100644 -index 6a155b779914828a0d4199bdfcb0d6fca25e1581..0000000000000000000000000000000000000000 ---- a/src/main/java/ca/spottedleaf/concurrentutil/lock/AreaLock.java -+++ /dev/null -@@ -1,146 +0,0 @@ --package ca.spottedleaf.concurrentutil.lock; -- --import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; --import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; --import java.util.ArrayList; --import java.util.List; --import java.util.concurrent.locks.LockSupport; -- --public final class AreaLock { -- -- private final int coordinateShift; -- -- private final Long2ReferenceOpenHashMap nodesByPosition = new Long2ReferenceOpenHashMap<>(1024, 0.10f); -- -- public AreaLock(final int coordinateShift) { -- this.coordinateShift = coordinateShift; -- } -- -- private static long key(final int x, final int z) { -- return ((long)z << 32) | (x & 0xFFFFFFFFL); -- } -- -- public Node lock(final int x, final int z, final int radius) { -- final Thread thread = Thread.currentThread(); -- final int minX = (x - radius) >> this.coordinateShift; -- final int minZ = (z - radius) >> this.coordinateShift; -- final int maxX = (x + radius) >> this.coordinateShift; -- final int maxZ = (z + radius) >> this.coordinateShift; -- -- final Node node = new Node(x, z, radius, thread); -- -- synchronized (this) { -- ReferenceOpenHashSet parents = null; -- for (int currZ = minZ; currZ <= maxZ; ++currZ) { -- for (int currX = minX; currX <= maxX; ++currX) { -- final Node dependency = this.nodesByPosition.put(key(currX, currZ), node); -- if (dependency == null) { -- continue; -- } -- -- if (parents == null) { -- parents = new ReferenceOpenHashSet<>(); -- } -- -- if (parents.add(dependency)) { -- // added a dependency, so we need to add as a child to the dependency -- if (dependency.children == null) { -- dependency.children = new ArrayList<>(); -- } -- dependency.children.add(node); -- } -- } -- } -- -- if (parents == null) { -- // no dependencies, so we can just return immediately -- return node; -- } // else: we need to lock -- -- node.parents = parents; -- } -- -- while (!node.unlocked) { -- LockSupport.park(node); -- } -- -- return node; -- } -- -- public void unlock(final Node node) { -- List toUnpark = null; -- -- final int x = node.x; -- final int z = node.z; -- final int radius = node.radius; -- -- final int minX = (x - radius) >> this.coordinateShift; -- final int minZ = (z - radius) >> this.coordinateShift; -- final int maxX = (x + radius) >> this.coordinateShift; -- final int maxZ = (z + radius) >> this.coordinateShift; -- -- synchronized (this) { -- final List children = node.children; -- if (children != null) { -- // try to unlock children -- for (int i = 0, len = children.size(); i < len; ++i) { -- final Node child = children.get(i); -- if (!child.parents.remove(node)) { -- throw new IllegalStateException(); -- } -- if (child.parents.isEmpty()) { -- // we can unlock, as it now has no dependencies in front -- child.parents = null; -- if (toUnpark == null) { -- toUnpark = new ArrayList<>(); -- toUnpark.add(child); -- } else { -- toUnpark.add(child); -- } -- } -- } -- } -- -- // remove node from dependency map -- for (int currZ = minZ; currZ <= maxZ; ++currZ) { -- for (int currX = minX; currX <= maxX; ++currX) { -- // node: we only remove if we match, as a mismatch indicates a child node which of course has not -- // yet been unlocked -- this.nodesByPosition.remove(key(currX, currZ), node); -- } -- } -- } -- -- if (toUnpark == null) { -- return; -- } -- -- // we move the unpark / unlock logic here because we want to avoid performing work while holding the lock -- -- for (int i = 0, len = toUnpark.size(); i < len; ++i) { -- final Node toUnlock = toUnpark.get(i); -- toUnlock.unlocked = true; // must be volatile and before unpark() -- LockSupport.unpark(toUnlock.thread); -- } -- } -- -- public static final class Node { -- -- public final int x; -- public final int z; -- public final int radius; -- public final Thread thread; -- -- private List children; -- private ReferenceOpenHashSet parents; -- -- private volatile boolean unlocked; -- -- public Node(final int x, final int z, final int radius, final Thread thread) { -- this.x = x; -- this.z = z; -- this.radius = radius; -- this.thread = thread; -- } -- } --} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java b/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4fd9a0cd8f1e6ae1a97e963dc7731a80bc6fac5b ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java -@@ -0,0 +1,395 @@ -+package ca.spottedleaf.concurrentutil.lock; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import it.unimi.dsi.fastutil.HashCommon; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.locks.LockSupport; -+ -+public final class ReentrantAreaLock { -+ -+ public final int coordinateShift; -+ -+ // aggressive load factor to reduce contention -+ private final ConcurrentHashMap nodes = new ConcurrentHashMap<>(128, 0.2f); -+ -+ public ReentrantAreaLock(final int coordinateShift) { -+ this.coordinateShift = coordinateShift; -+ } -+ -+ public boolean isHeldByCurrentThread(final int x, final int z) { -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int sectionX = x >> shift; -+ final int sectionZ = z >> shift; -+ -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ final Node node = this.nodes.get(coordinate); -+ -+ return node != null && node.thread == currThread; -+ } -+ -+ public boolean isHeldByCurrentThread(final int centerX, final int centerZ, final int radius) { -+ return this.isHeldByCurrentThread(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); -+ } -+ -+ public boolean isHeldByCurrentThread(final int fromX, final int fromZ, final int toX, final int toZ) { -+ if (fromX > toX || fromZ > toZ) { -+ throw new IllegalArgumentException(); -+ } -+ -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int fromSectionX = fromX >> shift; -+ final int fromSectionZ = fromZ >> shift; -+ final int toSectionX = toX >> shift; -+ final int toSectionZ = toZ >> shift; -+ -+ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { -+ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { -+ final Coordinate coordinate = new Coordinate(Coordinate.key(currX, currZ)); -+ -+ final Node node = this.nodes.get(coordinate); -+ -+ if (node == null || node.thread != currThread) { -+ return false; -+ } -+ } -+ } -+ -+ return true; -+ } -+ -+ public Node tryLock(final int x, final int z) { -+ return this.tryLock(x, z, x, z); -+ } -+ -+ public Node tryLock(final int centerX, final int centerZ, final int radius) { -+ return this.tryLock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); -+ } -+ -+ public Node tryLock(final int fromX, final int fromZ, final int toX, final int toZ) { -+ if (fromX > toX || fromZ > toZ) { -+ throw new IllegalArgumentException(); -+ } -+ -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int fromSectionX = fromX >> shift; -+ final int fromSectionZ = fromZ >> shift; -+ final int toSectionX = toX >> shift; -+ final int toSectionZ = toZ >> shift; -+ -+ final List areaAffected = new ArrayList<>(); -+ -+ final Node ret = new Node(this, areaAffected, currThread); -+ -+ boolean failed = false; -+ -+ // try to fast acquire area -+ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { -+ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { -+ final Coordinate coordinate = new Coordinate(Coordinate.key(currX, currZ)); -+ -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); -+ -+ if (prev == null) { -+ areaAffected.add(coordinate); -+ continue; -+ } -+ -+ if (prev.thread != currThread) { -+ failed = true; -+ break; -+ } -+ } -+ } -+ -+ if (!failed) { -+ return ret; -+ } -+ -+ // failed, undo logic -+ if (!areaAffected.isEmpty()) { -+ for (int i = 0, len = areaAffected.size(); i < len; ++i) { -+ final Coordinate key = areaAffected.get(i); -+ -+ if (this.nodes.remove(key) != ret) { -+ throw new IllegalStateException(); -+ } -+ } -+ -+ areaAffected.clear(); -+ -+ // since we inserted, we need to drain waiters -+ Thread unpark; -+ while ((unpark = ret.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ return null; -+ } -+ -+ public Node lock(final int x, final int z) { -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int sectionX = x >> shift; -+ final int sectionZ = z >> shift; -+ -+ final List areaAffected = new ArrayList<>(1); -+ -+ final Node ret = new Node(this, areaAffected, currThread); -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ -+ for (long failures = 0L;;) { -+ final Node park; -+ -+ // try to fast acquire area -+ { -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); -+ -+ if (prev == null) { -+ areaAffected.add(coordinate); -+ return ret; -+ } else if (prev.thread != currThread) { -+ park = prev; -+ } else { -+ // only one node we would want to acquire, and it's owned by this thread already -+ return ret; -+ } -+ } -+ -+ ++failures; -+ -+ if (failures > 128L && park.add(currThread)) { -+ LockSupport.park(); -+ } else { -+ // high contention, spin wait -+ if (failures < 128L) { -+ for (long i = 0; i < failures; ++i) { -+ Thread.onSpinWait(); -+ } -+ failures = failures << 1; -+ } else if (failures < 1_200L) { -+ LockSupport.parkNanos(1_000L); -+ failures = failures + 1L; -+ } else { // scale 0.1ms (100us) per failure -+ Thread.yield(); -+ LockSupport.parkNanos(100_000L * failures); -+ failures = failures + 1L; -+ } -+ } -+ } -+ } -+ -+ public Node lock(final int centerX, final int centerZ, final int radius) { -+ return this.lock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); -+ } -+ -+ public Node lock(final int fromX, final int fromZ, final int toX, final int toZ) { -+ if (fromX > toX || fromZ > toZ) { -+ throw new IllegalArgumentException(); -+ } -+ -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int fromSectionX = fromX >> shift; -+ final int fromSectionZ = fromZ >> shift; -+ final int toSectionX = toX >> shift; -+ final int toSectionZ = toZ >> shift; -+ -+ if (((fromSectionX ^ toSectionX) | (fromSectionZ ^ toSectionZ)) == 0) { -+ return this.lock(fromX, fromZ); -+ } -+ -+ final List areaAffected = new ArrayList<>(); -+ -+ final Node ret = new Node(this, areaAffected, currThread); -+ -+ for (long failures = 0L;;) { -+ Node park = null; -+ boolean addedToArea = false; -+ boolean alreadyOwned = false; -+ boolean allOwned = true; -+ -+ // try to fast acquire area -+ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { -+ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { -+ final Coordinate coordinate = new Coordinate(Coordinate.key(currX, currZ)); -+ -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); -+ -+ if (prev == null) { -+ addedToArea = true; -+ allOwned = false; -+ areaAffected.add(coordinate); -+ continue; -+ } -+ -+ if (prev.thread != currThread) { -+ park = prev; -+ alreadyOwned = true; -+ break; -+ } -+ } -+ } -+ -+ if (park == null) { -+ if (alreadyOwned && !allOwned) { -+ throw new IllegalStateException("Improper lock usage: Should never acquire intersecting areas"); -+ } -+ return ret; -+ } -+ -+ // failed, undo logic -+ if (addedToArea) { -+ for (int i = 0, len = areaAffected.size(); i < len; ++i) { -+ final Coordinate key = areaAffected.get(i); -+ -+ if (this.nodes.remove(key) != ret) { -+ throw new IllegalStateException(); -+ } -+ } -+ -+ areaAffected.clear(); -+ -+ // since we inserted, we need to drain waiters -+ Thread unpark; -+ while ((unpark = ret.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ ++failures; -+ -+ if (failures > 128L && park.add(currThread)) { -+ LockSupport.park(park); -+ } else { -+ // high contention, spin wait -+ if (failures < 128L) { -+ for (long i = 0; i < failures; ++i) { -+ Thread.onSpinWait(); -+ } -+ failures = failures << 1; -+ } else if (failures < 1_200L) { -+ LockSupport.parkNanos(1_000L); -+ failures = failures + 1L; -+ } else { // scale 0.1ms (100us) per failure -+ Thread.yield(); -+ LockSupport.parkNanos(100_000L * failures); -+ failures = failures + 1L; -+ } -+ } -+ -+ if (addedToArea) { -+ // try again, so we need to allow adds so that other threads can properly block on us -+ ret.allowAdds(); -+ } -+ } -+ } -+ -+ public void unlock(final Node node) { -+ if (node.lock != this) { -+ throw new IllegalStateException("Unlock target lock mismatch"); -+ } -+ -+ final List areaAffected = node.areaAffected; -+ -+ if (areaAffected.isEmpty()) { -+ // here we are not in the node map, and so do not need to remove from the node map or unblock any waiters -+ return; -+ } -+ -+ // remove from node map; allowing other threads to lock -+ for (int i = 0, len = areaAffected.size(); i < len; ++i) { -+ final Coordinate coordinate = areaAffected.get(i); -+ if (this.nodes.remove(coordinate) != node) { -+ throw new IllegalStateException(); -+ } -+ } -+ -+ Thread unpark; -+ while ((unpark = node.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ public static final class Node extends MultiThreadedQueue { -+ -+ private final ReentrantAreaLock lock; -+ private final List areaAffected; -+ private final Thread thread; -+ //private final Throwable WHO_CREATED_MY_ASS = new Throwable(); -+ -+ private Node(final ReentrantAreaLock lock, final List areaAffected, final Thread thread) { -+ this.lock = lock; -+ this.areaAffected = areaAffected; -+ this.thread = thread; -+ } -+ -+ @Override -+ public String toString() { -+ return "Node{" + -+ "areaAffected=" + this.areaAffected + -+ ", thread=" + this.thread + -+ '}'; -+ } -+ } -+ -+ private static final class Coordinate implements Comparable { -+ -+ public final long key; -+ -+ public Coordinate(final long key) { -+ this.key = key; -+ } -+ -+ public Coordinate(final int x, final int z) { -+ this.key = key(x, z); -+ } -+ -+ public static long key(final int x, final int z) { -+ return ((long)z << 32) | (x & 0xFFFFFFFFL); -+ } -+ -+ public static int x(final long key) { -+ return (int)key; -+ } -+ -+ public static int z(final long key) { -+ return (int)(key >>> 32); -+ } -+ -+ @Override -+ public int hashCode() { -+ return (int)HashCommon.mix(this.key); -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ -+ if (!(obj instanceof Coordinate other)) { -+ return false; -+ } -+ -+ return this.key == other.key; -+ } -+ -+ // This class is intended for HashMap/ConcurrentHashMap usage, which do treeify bin nodes if the chain -+ // is too large. So we should implement compareTo to help. -+ @Override -+ public int compareTo(final Coordinate other) { -+ return Long.compare(this.key, other.key); -+ } -+ -+ @Override -+ public String toString() { -+ return "[" + x(this.key) + "," + z(this.key) + "]"; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/concurrentutil/lock/SyncReentrantAreaLock.java b/src/main/java/ca/spottedleaf/concurrentutil/lock/SyncReentrantAreaLock.java -new file mode 100644 -index 0000000000000000000000000000000000000000..64b5803d002b2968841a5ddee987f98b72964e87 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/concurrentutil/lock/SyncReentrantAreaLock.java -@@ -0,0 +1,217 @@ -+package ca.spottedleaf.concurrentutil.lock; -+ -+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue; -+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import java.util.concurrent.locks.LockSupport; -+ -+// not concurrent, unlike ReentrantAreaLock -+// no incorrect lock usage detection (acquiring intersecting areas) -+// this class is nothing more than a performance reference for ReentrantAreaLock -+public final class SyncReentrantAreaLock { -+ -+ private final int coordinateShift; -+ -+ // aggressive load factor to reduce contention -+ private final Long2ReferenceOpenHashMap nodes = new Long2ReferenceOpenHashMap<>(128, 0.2f); -+ -+ public SyncReentrantAreaLock(final int coordinateShift) { -+ this.coordinateShift = coordinateShift; -+ } -+ -+ private static long key(final int x, final int z) { -+ return ((long)z << 32) | (x & 0xFFFFFFFFL); -+ } -+ -+ public Node lock(final int x, final int z) { -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int sectionX = x >> shift; -+ final int sectionZ = z >> shift; -+ -+ final LongArrayList areaAffected = new LongArrayList(); -+ -+ final Node ret = new Node(this, areaAffected, currThread); -+ -+ final long coordinate = key(sectionX, sectionZ); -+ -+ for (long failures = 0L;;) { -+ final Node park; -+ -+ synchronized (this) { -+ // try to fast acquire area -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); -+ -+ if (prev == null) { -+ areaAffected.add(coordinate); -+ return ret; -+ } else if (prev.thread != currThread) { -+ park = prev; -+ } else { -+ // only one node we would want to acquire, and it's owned by this thread already -+ return ret; -+ } -+ } -+ -+ ++failures; -+ -+ if (failures > 128L && park.add(currThread)) { -+ LockSupport.park(); -+ } else { -+ // high contention, spin wait -+ if (failures < 128L) { -+ for (long i = 0; i < failures; ++i) { -+ Thread.onSpinWait(); -+ } -+ failures = failures << 1; -+ } else if (failures < 1_200L) { -+ LockSupport.parkNanos(1_000L); -+ failures = failures + 1L; -+ } else { // scale 0.1ms (100us) per failure -+ Thread.yield(); -+ LockSupport.parkNanos(100_000L * failures); -+ failures = failures + 1L; -+ } -+ } -+ } -+ } -+ -+ public Node lock(final int centerX, final int centerZ, final int radius) { -+ return this.lock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius); -+ } -+ -+ public Node lock(final int fromX, final int fromZ, final int toX, final int toZ) { -+ if (fromX > toX || fromZ > toZ) { -+ throw new IllegalArgumentException(); -+ } -+ -+ final Thread currThread = Thread.currentThread(); -+ final int shift = this.coordinateShift; -+ final int fromSectionX = fromX >> shift; -+ final int fromSectionZ = fromZ >> shift; -+ final int toSectionX = toX >> shift; -+ final int toSectionZ = toZ >> shift; -+ -+ final LongArrayList areaAffected = new LongArrayList(); -+ -+ final Node ret = new Node(this, areaAffected, currThread); -+ -+ for (long failures = 0L;;) { -+ Node park = null; -+ boolean addedToArea = false; -+ -+ synchronized (this) { -+ // try to fast acquire area -+ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) { -+ for (int currX = fromSectionX; currX <= toSectionX; ++currX) { -+ final long coordinate = key(currX, currZ); -+ -+ final Node prev = this.nodes.putIfAbsent(coordinate, ret); -+ -+ if (prev == null) { -+ addedToArea = true; -+ areaAffected.add(coordinate); -+ continue; -+ } -+ -+ if (prev.thread != currThread) { -+ park = prev; -+ break; -+ } -+ } -+ } -+ -+ if (park == null) { -+ return ret; -+ } -+ -+ // failed, undo logic -+ if (!areaAffected.isEmpty()) { -+ for (int i = 0, len = areaAffected.size(); i < len; ++i) { -+ final long key = areaAffected.getLong(i); -+ -+ if (!this.nodes.remove(key, ret)) { -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ } -+ -+ if (addedToArea) { -+ areaAffected.clear(); -+ // since we inserted, we need to drain waiters -+ Thread unpark; -+ while ((unpark = ret.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ ++failures; -+ -+ if (failures > 128L && park.add(currThread)) { -+ LockSupport.park(); -+ } else { -+ // high contention, spin wait -+ if (failures < 128L) { -+ for (long i = 0; i < failures; ++i) { -+ Thread.onSpinWait(); -+ } -+ failures = failures << 1; -+ } else if (failures < 1_200L) { -+ LockSupport.parkNanos(1_000L); -+ failures = failures + 1L; -+ } else { // scale 0.1ms (100us) per failure -+ Thread.yield(); -+ LockSupport.parkNanos(100_000L * failures); -+ failures = failures + 1L; -+ } -+ } -+ -+ if (addedToArea) { -+ // try again, so we need to allow adds so that other threads can properly block on us -+ ret.allowAdds(); -+ } -+ } -+ } -+ -+ public void unlock(final Node node) { -+ if (node.lock != this) { -+ throw new IllegalStateException("Unlock target lock mismatch"); -+ } -+ -+ final LongArrayList areaAffected = node.areaAffected; -+ -+ if (areaAffected.isEmpty()) { -+ // here we are not in the node map, and so do not need to remove from the node map or unblock any waiters -+ return; -+ } -+ -+ // remove from node map; allowing other threads to lock -+ synchronized (this) { -+ for (int i = 0, len = areaAffected.size(); i < len; ++i) { -+ final long coordinate = areaAffected.getLong(i); -+ if (!this.nodes.remove(coordinate, node)) { -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ -+ Thread unpark; -+ while ((unpark = node.pollOrBlockAdds()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ public static final class Node extends MultiThreadedQueue { -+ -+ private final SyncReentrantAreaLock lock; -+ private final LongArrayList areaAffected; -+ private final Thread thread; -+ -+ private Node(final SyncReentrantAreaLock lock, final LongArrayList areaAffected, final Thread thread) { -+ this.lock = lock; -+ this.areaAffected = areaAffected; -+ this.thread = thread; -+ } -+ } -+} -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 db18f2947ef9d2863e3a029f0500343920cba1db..8580ddfeb9cab5ba3d7c5cea836bdb67e49bab50 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 -@@ -76,10 +76,50 @@ public final class ChunkHolderManager { - // this field contains chunk holders that were created in addTicketAtLevel - // because the chunk holders were created without a reliable unload hook (i.e creation for entity/poi loading, - // which always check for unload after their tasks finish) we need to do that ourselves later -- private final ReferenceOpenHashSet specialCaseUnload = new ReferenceOpenHashSet<>(); -+ // Folia - use area based lock to reduce contention - no longer needed - // Folia end - region threading - -- public final ReentrantLock ticketLock = new ReentrantLock(); // Folia - region threading -+ // Folia - use area based lock to reduce contention -+ // Folia start - use area based lock to reduce contention -+ public final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock ticketLockArea = new ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock(ChunkTaskScheduler.getChunkSystemLockShift()); -+ -+ private final java.util.concurrent.ConcurrentHashMap>> tickets = new java.util.concurrent.ConcurrentHashMap<>(); -+ private final java.util.concurrent.ConcurrentHashMap sectionToChunkToExpireCount = new java.util.concurrent.ConcurrentHashMap<>(); -+ -+ public boolean processTicketUpdates(final int posX, final int posZ) { -+ final int ticketShift = io.papermc.paper.threadedregions.ThreadedTicketLevelPropagator.SECTION_SHIFT; -+ final int ticketMask = (1 << ticketShift) - 1; -+ final List scheduledTasks = new ArrayList<>(); -+ final List changedFullStatus = new ArrayList<>(); -+ final boolean ret; -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock( -+ ((posX >> ticketShift) - 1) << ticketShift, -+ ((posZ >> ticketShift) - 1) << ticketShift, -+ (((posX >> ticketShift) + 1) << ticketShift) | ticketMask, -+ (((posZ >> ticketShift) + 1) << ticketShift) | ticketMask -+ ); -+ try { -+ ret = this.processTicketUpdatesNoLock(posX >> ticketShift, posZ >> ticketShift, scheduledTasks, changedFullStatus); -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ -+ this.addChangedStatuses(changedFullStatus); -+ -+ for (int i = 0, len = scheduledTasks.size(); i < len; ++i) { -+ scheduledTasks.get(i).schedule(); -+ } -+ -+ return ret; -+ } -+ -+ private boolean processTicketUpdatesNoLock(final int sectionX, final int sectionZ, final List scheduledTasks, -+ final List changedFullStatus) { -+ return this.ticketLevelPropagator.performUpdate( -+ sectionX, sectionZ, this.taskScheduler.schedulingLockArea, scheduledTasks, changedFullStatus -+ ); -+ } -+ // Folia end - use area based lock to reduce contention - - private final SWMRLong2ObjectHashTable chunkHolders = new SWMRLong2ObjectHashTable<>(16384, 0.25f); - // Folia - region threading -@@ -119,10 +159,7 @@ public final class ChunkHolderManager { - return Long.compare(coord1, coord2); - }); - private long currentTick; -- private final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap<>(8192, 0.25f); -- // what a disaster of a name -- // this is a map of removal tick to a map of chunks and the number of tickets a chunk has that are to expire that tick -- private final Long2ObjectOpenHashMap removeTickToChunkExpireTicketCount = new Long2ObjectOpenHashMap<>(); -+ // Folia - use area based lock to reduce contention - moved to global state - - public void merge(final HolderManagerRegionData into, final long tickOffset) { - // Order doesn't really matter for the pending full update... -@@ -136,34 +173,7 @@ public final class ChunkHolderManager { - into.autoSaveQueue.add(holder); - } - -- final long chunkManagerTickOffset = into.currentTick - this.currentTick; -- for (final Iterator>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); -- iterator.hasNext();) { -- final Long2ObjectMap.Entry>> entry = iterator.next(); -- final SortedArraySet> oldTickets = entry.getValue(); -- final SortedArraySet> newTickets = SortedArraySet.create(Math.max(4, oldTickets.size() + 1)); -- for (final Ticket ticket : oldTickets) { -- newTickets.add( -- new Ticket(ticket.getType(), ticket.getTicketLevel(), ticket.key, -- ticket.removalTick == NO_TIMEOUT_MARKER ? NO_TIMEOUT_MARKER : ticket.removalTick + chunkManagerTickOffset) -- ); -- } -- into.tickets.put(entry.getLongKey(), newTickets); -- } -- for (final Iterator> iterator = this.removeTickToChunkExpireTicketCount.long2ObjectEntrySet().fastIterator(); -- iterator.hasNext();) { -- final Long2ObjectMap.Entry entry = iterator.next(); -- into.removeTickToChunkExpireTicketCount.merge( -- (long)(entry.getLongKey() + chunkManagerTickOffset), entry.getValue(), -- (final Long2IntOpenHashMap t, final Long2IntOpenHashMap f) -> { -- for (final Iterator itr = f.long2IntEntrySet().fastIterator(); itr.hasNext();) { -- final Long2IntMap.Entry e = itr.next(); -- t.addTo(e.getLongKey(), e.getIntValue()); -- } -- return t; -- } -- ); -- } -+ // Folia - use area based lock to reduce contention - moved to global state - } - - public void split(final int chunkToRegionShift, final Long2ReferenceOpenHashMap regionToData, -@@ -190,37 +200,7 @@ public final class ChunkHolderManager { - for (final HolderManagerRegionData data : dataSet) { - data.currentTick = this.currentTick; - } -- for (final Iterator>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); -- iterator.hasNext();) { -- final Long2ObjectMap.Entry>> entry = iterator.next(); -- final long chunkKey = entry.getLongKey(); -- final int regionCoordinateX = CoordinateUtils.getChunkX(chunkKey) >> chunkToRegionShift; -- final int regionCoordinateZ = CoordinateUtils.getChunkZ(chunkKey) >> chunkToRegionShift; -- -- // can never be null, since a chunk holder exists if the ticket set is not empty -- regionToData.get(CoordinateUtils.getChunkKey(regionCoordinateX, regionCoordinateZ)).tickets.put(chunkKey, entry.getValue()); -- } -- for (final Iterator> iterator = this.removeTickToChunkExpireTicketCount.long2ObjectEntrySet().fastIterator(); -- iterator.hasNext();) { -- final Long2ObjectMap.Entry entry = iterator.next(); -- final long tick = entry.getLongKey(); -- final Long2IntOpenHashMap chunkToCount = entry.getValue(); -- -- for (final Iterator itr = chunkToCount.long2IntEntrySet().fastIterator(); itr.hasNext();) { -- final Long2IntMap.Entry e = itr.next(); -- final long chunkKey = e.getLongKey(); -- final int regionCoordinateX = CoordinateUtils.getChunkX(chunkKey) >> chunkToRegionShift; -- final int regionCoordinateZ = CoordinateUtils.getChunkZ(chunkKey) >> chunkToRegionShift; -- final int count = e.getIntValue(); -- -- // can never be null, since a chunk holder exists if the ticket set is not empty -- final HolderManagerRegionData data = regionToData.get(CoordinateUtils.getChunkKey(regionCoordinateX, regionCoordinateZ)); -- -- data.removeTickToChunkExpireTicketCount.computeIfAbsent(tick, (final long keyInMap) -> { -- return new Long2IntOpenHashMap(); -- }).put(chunkKey, count); -- } -- } -+ // Folia - use area based lock to reduce contention - moved to global state - } - } - -@@ -239,38 +219,21 @@ public final class ChunkHolderManager { - return region.getData().getHolderManagerRegionData(); - } - -- // MUST hold ticket lock -- private ChunkHolderManager.HolderManagerRegionData getDataFor(final long key) { -- return this.getDataFor(CoordinateUtils.getChunkX(key), CoordinateUtils.getChunkZ(key)); -- } -- -- // MUST hold ticket lock -- private ChunkHolderManager.HolderManagerRegionData getDataFor(final int chunkX, final int chunkZ) { -- if (!this.ticketLock.isHeldByCurrentThread()) { -- throw new IllegalStateException("Must hold ticket level lock"); -- } -- -- final ThreadedRegionizer.ThreadedRegion region -- = this.world.regioniser.getRegionAtUnsynchronised(chunkX, chunkZ); -- -- if (region == null) { -- return null; -- } -- -- return region.getData().getHolderManagerRegionData(); -- } -- // Folia end - region threading -+ // Folia - use area based lock to reduce contention - - - public ChunkHolderManager(final ServerLevel world, final ChunkTaskScheduler taskScheduler) { - this.world = world; - this.taskScheduler = taskScheduler; -+ // Folia start - use area based lock to reduce contention -+ this.unloadQueue = new io.papermc.paper.threadedregions.ChunkQueue(world.regioniser.sectionChunkShift); -+ // Folia end - use area based lock to reduce contention - } - -- private long statusUpgradeId; -+ private final java.util.concurrent.atomic.AtomicLong statusUpgradeId = new java.util.concurrent.atomic.AtomicLong(); // Folia - use area based lock to reduce contention - - long getNextStatusUpgradeId() { -- return ++this.statusUpgradeId; -+ return this.statusUpgradeId.incrementAndGet(); // Folia - use area based lock to reduce contention - } - - public List getOldChunkHolders() { -@@ -459,22 +422,65 @@ public final class ChunkHolderManager { - } - } - -- protected final Long2IntLinkedOpenHashMap ticketLevelUpdates = new Long2IntLinkedOpenHashMap() { -+ // Folia start - use area based lock to reduce contention -+ protected final io.papermc.paper.threadedregions.ThreadedTicketLevelPropagator ticketLevelPropagator = new io.papermc.paper.threadedregions.ThreadedTicketLevelPropagator() { - @Override -- protected void rehash(final int newN) { -- // no downsizing allowed -- if (newN < this.n) { -- return; -+ protected void processLevelUpdates(final it.unimi.dsi.fastutil.longs.Long2ByteLinkedOpenHashMap updates) { -+ // first the necessary chunkholders must be created, so just update the ticket levels -+ for (final Iterator iterator = updates.long2ByteEntrySet().fastIterator(); iterator.hasNext();) { -+ final it.unimi.dsi.fastutil.longs.Long2ByteMap.Entry entry = iterator.next(); -+ final long key = entry.getLongKey(); -+ final int newLevel = convertBetweenTicketLevels((int)entry.getByteValue()); -+ -+ NewChunkHolder current = ChunkHolderManager.this.chunkHolders.get(key); -+ if (current == null && newLevel > MAX_TICKET_LEVEL) { -+ // not loaded and it shouldn't be loaded! -+ iterator.remove(); -+ continue; -+ } -+ -+ final int currentLevel = current == null ? MAX_TICKET_LEVEL + 1 : current.getCurrentTicketLevel(); -+ if (currentLevel == newLevel) { -+ // nothing to do -+ iterator.remove(); -+ continue; -+ } -+ -+ if (current == null) { -+ // must create -+ current = ChunkHolderManager.this.createChunkHolder(key); -+ synchronized (ChunkHolderManager.this.chunkHolders) { -+ ChunkHolderManager.this.chunkHolders.put(key, current); -+ } -+ current.updateTicketLevel(newLevel); -+ } else { -+ current.updateTicketLevel(newLevel); -+ } - } -- super.rehash(newN); - } -- }; - -- protected final Delayed8WayDistancePropagator2D ticketLevelPropagator = new Delayed8WayDistancePropagator2D( -- (final long coordinate, final byte oldLevel, final byte newLevel) -> { -- ChunkHolderManager.this.ticketLevelUpdates.putAndMoveToLast(coordinate, convertBetweenTicketLevels(newLevel)); -+ @Override -+ protected void processSchedulingUpdates(final it.unimi.dsi.fastutil.longs.Long2ByteLinkedOpenHashMap updates, final List scheduledTasks, -+ final List changedFullStatus) { -+ final List prev = CURRENT_TICKET_UPDATE_SCHEDULING.get(); -+ CURRENT_TICKET_UPDATE_SCHEDULING.set(scheduledTasks); -+ try { -+ for (final LongIterator iterator = updates.keySet().iterator(); iterator.hasNext();) { -+ final long key = iterator.nextLong(); -+ final NewChunkHolder current = ChunkHolderManager.this.chunkHolders.get(key); -+ -+ if (current == null) { -+ throw new IllegalStateException("Expected chunk holder to be created"); -+ } -+ -+ current.processTicketLevelUpdate(scheduledTasks, changedFullStatus); -+ } -+ } finally { -+ CURRENT_TICKET_UPDATE_SCHEDULING.set(prev); - } -- ); -+ } -+ }; -+ // Folia end - use area based lock to reduce contention - // function for converting between ticket levels and propagator levels and vice versa - // the problem is the ticket level propagator will propagate from a set source down to zero, whereas mojang expects - // levels to propagate from a set value up to a maximum value. so we need to convert the levels we put into the propagator -@@ -489,46 +495,72 @@ public final class ChunkHolderManager { - } - - public String getTicketDebugString(final long coordinate) { -- this.ticketLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(CoordinateUtils.getChunkX(coordinate), CoordinateUtils.getChunkZ(coordinate)); // Folia - use area based lock to reduce contention - try { -- // Folia start - region threading -- final ChunkHolderManager.HolderManagerRegionData holderManagerRegionData = this.getDataFor(coordinate); -- final SortedArraySet> tickets = holderManagerRegionData == null ? null : holderManagerRegionData.tickets.get(coordinate); -- // Folia end - region threading -+ final SortedArraySet> tickets = this.tickets.get(new RegionFileIOThread.ChunkCoordinate(coordinate)); // Folia - use area based lock to reduce contention - - return tickets != null ? tickets.first().toString() : "no_ticket"; - } finally { -- this.ticketLock.unlock(); -+ // Folia start - use area based lock to reduce contention -+ if (ticketLock != null) { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ // Folia end - use area based lock to reduce contention - } - } - - public Long2ObjectOpenHashMap>> getTicketsCopy() { -- this.ticketLock.lock(); -- try { -- // Folia start - region threading -- Long2ObjectOpenHashMap>> ret = new Long2ObjectOpenHashMap<>(); -- this.world.regioniser.computeForAllRegions((region) -> { -- for (final LongIterator iterator = region.getData().getHolderManagerRegionData().tickets.keySet().longIterator(); iterator.hasNext();) { -- final long chunk = iterator.nextLong(); -+ // Folia start - use area based lock to reduce contention -+ final Long2ObjectOpenHashMap>> ret = new Long2ObjectOpenHashMap<>(); -+ final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap> sections = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap(); -+ final int sectionShift = ChunkTaskScheduler.getChunkSystemLockShift(); -+ for (final RegionFileIOThread.ChunkCoordinate coord : this.tickets.keySet()) { -+ sections.computeIfAbsent( -+ CoordinateUtils.getChunkKey( -+ CoordinateUtils.getChunkX(coord.key) >> sectionShift, -+ CoordinateUtils.getChunkZ(coord.key) >> sectionShift -+ ), -+ (final long keyInMap) -> { -+ return new ArrayList<>(); -+ } -+ ).add(coord); -+ } - -- ret.put(chunk, region.getData().getHolderManagerRegionData().tickets.get(chunk)); -+ for (final Iterator>> iterator = sections.long2ObjectEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry> entry = iterator.next(); -+ final long sectionKey = entry.getLongKey(); -+ final List coordinates = entry.getValue(); -+ -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock( -+ CoordinateUtils.getChunkX(sectionKey) << sectionShift, -+ CoordinateUtils.getChunkZ(sectionKey) << sectionShift -+ ); -+ try { -+ for (final RegionFileIOThread.ChunkCoordinate coord : coordinates) { -+ final SortedArraySet> tickets = this.tickets.get(coord); -+ if (tickets == null) { -+ // removed before we acquired lock -+ continue; -+ } -+ ret.put(coord.key, new SortedArraySet<>(tickets)); - } -- }); -- return ret; -- // Folia end - region threading -- } finally { -- this.ticketLock.unlock(); -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } - } -+ -+ return ret; -+ // Folia end - use area based lock to reduce contention - } - - public Collection getPluginChunkTickets(int x, int z) { - ImmutableList.Builder ret; -- this.ticketLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(x, z); // Folia - use area based lock to reduce contention - try { - // Folia start - region threading - final long coordinate = CoordinateUtils.getChunkKey(x, z); -- final ChunkHolderManager.HolderManagerRegionData holderManagerRegionData = this.getDataFor(coordinate); -- final SortedArraySet> tickets = holderManagerRegionData == null ? null : holderManagerRegionData.tickets.get(coordinate); -+ final SortedArraySet> tickets = this.tickets.get(new RegionFileIOThread.ChunkCoordinate(coordinate)); // Folia - use area based lock to reduce contention - // Folia end - region threading - - if (tickets == null) { -@@ -542,21 +574,19 @@ public final class ChunkHolderManager { - } - } - } finally { -- this.ticketLock.unlock(); -+ this.ticketLockArea.unlock(ticketLock); // Folia - use area based lock to reduce contention - } - - return ret.build(); - } - -- protected final int getPropagatedTicketLevel(final long coordinate) { -- return convertBetweenTicketLevels(this.ticketLevelPropagator.getLevel(coordinate)); -- } -+ // Folia - use area based lock to reduce contention - method not needed, TODO rebase removal - - protected final void updateTicketLevel(final long coordinate, final int ticketLevel) { - if (ticketLevel > ChunkMap.MAX_CHUNK_DISTANCE) { -- this.ticketLevelPropagator.removeSource(coordinate); -+ this.ticketLevelPropagator.removeSource(CoordinateUtils.getChunkX(coordinate), CoordinateUtils.getChunkZ(coordinate)); // Folia - use area based lock to reduce contention - } else { -- this.ticketLevelPropagator.setSource(coordinate, convertBetweenTicketLevels(ticketLevel)); -+ this.ticketLevelPropagator.setSource(CoordinateUtils.getChunkX(coordinate), CoordinateUtils.getChunkZ(coordinate), convertBetweenTicketLevels(ticketLevel)); // Folia - use area based lock to reduce contention - } - } - -@@ -574,45 +604,66 @@ public final class ChunkHolderManager { - return this.addTicketAtLevel(type, CoordinateUtils.getChunkKey(chunkX, chunkZ), level, identifier); - } - -+ // Folia start - use area based lock to reduce contention -+ private void addExpireCount(final int chunkX, final int chunkZ) { -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ -+ final int sectionShift = TickRegions.getRegionChunkShift(); -+ final RegionFileIOThread.ChunkCoordinate sectionKey = new RegionFileIOThread.ChunkCoordinate(CoordinateUtils.getChunkKey( -+ chunkX >> sectionShift, -+ chunkZ >> sectionShift -+ )); -+ -+ this.sectionToChunkToExpireCount.computeIfAbsent(sectionKey, (final RegionFileIOThread.ChunkCoordinate keyInMap) -> { -+ return new Long2IntOpenHashMap(); -+ }).addTo(chunkKey, 1); -+ } -+ -+ private void removeExpireCount(final int chunkX, final int chunkZ) { -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ -+ final int sectionShift = TickRegions.getRegionChunkShift(); -+ final RegionFileIOThread.ChunkCoordinate sectionKey = new RegionFileIOThread.ChunkCoordinate(CoordinateUtils.getChunkKey( -+ chunkX >> sectionShift, -+ chunkZ >> sectionShift -+ )); -+ -+ final Long2IntOpenHashMap removeCounts = this.sectionToChunkToExpireCount.get(sectionKey); -+ final int prevCount = removeCounts.addTo(chunkKey, -1); -+ -+ if (prevCount == 1) { -+ removeCounts.remove(chunkKey); -+ if (removeCounts.isEmpty()) { -+ this.sectionToChunkToExpireCount.remove(sectionKey); -+ } -+ } -+ } -+ // Folia end - use area based lock to reduce contention -+ - // supposed to return true if the ticket was added and did not replace another - // but, we always return false if the ticket cannot be added - public boolean addTicketAtLevel(final TicketType type, final long chunk, final int level, final T identifier) { -- final long removeDelay = Math.max(0, type.timeout); -+ // Folia start - use area based lock to reduce contention -+ return this.addTicketAtLevel(type, chunk, level, identifier, true); -+ } -+ boolean addTicketAtLevel(final TicketType type, final long chunk, final int level, final T identifier, final boolean lock) { -+ final long removeDelay = type.timeout <= 0 ? NO_TIMEOUT_MARKER : type.timeout; -+ // Folia end - use area based lock to reduce contention - if (level > MAX_TICKET_LEVEL) { - return false; - } - -- // Folia start - region threading -- final ThreadedRegionizer.ThreadedRegion currRegion = TickRegionScheduler.getCurrentRegion(); -- final boolean lock = currRegion == null || this.world.regioniser.getRegionAtUnsynchronised( -- CoordinateUtils.getChunkX(chunk), CoordinateUtils.getChunkZ(chunk) -- ) != currRegion; -- // Folia end - region threading -+ // Folia start - use area based lock to reduce contention -+ final int chunkX = CoordinateUtils.getChunkX(chunk); -+ final int chunkZ = CoordinateUtils.getChunkZ(chunk); -+ final RegionFileIOThread.ChunkCoordinate chunkCoord = new RegionFileIOThread.ChunkCoordinate(chunk); -+ final Ticket ticket = new Ticket<>(type, level, identifier, removeDelay); - -- this.ticketLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = lock ? this.ticketLockArea.lock(chunkX, chunkZ) : null; - try { -- // Folia start - region threading -- NewChunkHolder holder = this.chunkHolders.get(chunk); -- if (holder == null) { -- // we need to guarantee that a chunk holder exists for each ticket -- // this must be executed before retrieving the holder manager data for a target chunk, to ensure the -- // region will exist -- this.chunkHolders.put(chunk, holder = this.createChunkHolder(chunk)); -- this.specialCaseUnload.add(holder); -- } -+ // Folia end - use area based lock to reduce contention - -- if (lock) { -- // we just need to prevent merging, so we only need the read lock -- // additionally, this will prevent deadlock in the remove all tickets function by using the read lock -- this.world.regioniser.acquireReadLock(); -- } -- try { -- final ChunkHolderManager.HolderManagerRegionData targetData = lock ? this.getDataFor(chunk) : currRegion.getData().getHolderManagerRegionData(); -- // Folia end - region threading -- final long removeTick = removeDelay == 0 ? NO_TIMEOUT_MARKER : targetData.currentTick + removeDelay; // Folia - region threading -- final Ticket ticket = new Ticket<>(type, level, identifier, removeTick); -- -- final SortedArraySet> ticketsAtChunk = targetData.tickets.computeIfAbsent(chunk, (final long keyInMap) -> { // Folia - region threading -+ final SortedArraySet> ticketsAtChunk = this.tickets.computeIfAbsent(chunkCoord, (final RegionFileIOThread.ChunkCoordinate keyInMap) -> { // Folia - region threading // Folia - use area based lock to reduce contention - return SortedArraySet.create(4); - }); - -@@ -621,30 +672,20 @@ public final class ChunkHolderManager { - final int levelAfter = getTicketLevelAt(ticketsAtChunk); - - if (current != ticket) { -- final long oldRemovalTick = current.removalTick; -- if (removeTick != oldRemovalTick) { -- if (oldRemovalTick != NO_TIMEOUT_MARKER) { -- final Long2IntOpenHashMap removeCounts = targetData.removeTickToChunkExpireTicketCount.get(oldRemovalTick); // Folia - region threading -- final int prevCount = removeCounts.addTo(chunk, -1); -- -- if (prevCount == 1) { -- removeCounts.remove(chunk); -- if (removeCounts.isEmpty()) { -- targetData.removeTickToChunkExpireTicketCount.remove(oldRemovalTick); // Folia - region threading -- } -- } -- } -- if (removeTick != NO_TIMEOUT_MARKER) { -- targetData.removeTickToChunkExpireTicketCount.computeIfAbsent(removeTick, (final long keyInMap) -> { // Folia - region threading -- return new Long2IntOpenHashMap(); -- }).addTo(chunk, 1); -+ final long oldRemoveDelay = current.removeDelay; // Folia - use area based lock to reduce contention -+ // Folia start - use area based lock to reduce contention -+ if (removeDelay != oldRemoveDelay) { -+ if (oldRemoveDelay != NO_TIMEOUT_MARKER && removeDelay == NO_TIMEOUT_MARKER) { -+ this.removeExpireCount(chunkX, chunkZ); -+ } else if (oldRemoveDelay == NO_TIMEOUT_MARKER) { -+ // since old != new, we have that NO_TIMEOUT_MARKER != new -+ this.addExpireCount(chunkX, chunkZ); -+ // Folia end - use area based lock to reduce contention - } - } - } else { -- if (removeTick != NO_TIMEOUT_MARKER) { -- targetData.removeTickToChunkExpireTicketCount.computeIfAbsent(removeTick, (final long keyInMap) -> { // Folia - region threading -- return new Long2IntOpenHashMap(); -- }).addTo(chunk, 1); -+ if (removeDelay != NO_TIMEOUT_MARKER) { -+ this.addExpireCount(chunkX, chunkZ); // Folia - use area based lock to reduce contention - } - } - -@@ -653,13 +694,13 @@ public final class ChunkHolderManager { - } - - return current == ticket; -- } finally { // Folia start - region threading -- if (lock) { -- this.world.regioniser.releaseReadLock(); -- } -- } // Folia end - region threading -+ // Folia - use area based lock to reduce contention - } finally { -- this.ticketLock.unlock(); -+ // Folia start - use area based lock to reduce contention -+ if (ticketLock != null) { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ // Folia end - use area based lock to reduce contention - } - } - -@@ -672,117 +713,104 @@ public final class ChunkHolderManager { - } - - public boolean removeTicketAtLevel(final TicketType type, final long chunk, final int level, final T identifier) { -+ // Folia start - use area based lock to reduce contention -+ return this.removeTicketAtLevel(type, chunk, level, identifier, true); -+ } -+ boolean removeTicketAtLevel(final TicketType type, final long chunk, final int level, final T identifier, final boolean lock) { -+ // Folia end - use area based lock to reduce contention - if (level > MAX_TICKET_LEVEL) { - return false; - } - -- // Folia start - region threading -- final ThreadedRegionizer.ThreadedRegion currRegion = TickRegionScheduler.getCurrentRegion(); -- final boolean lock = currRegion == null || this.world.regioniser.getRegionAtUnsynchronised( -- CoordinateUtils.getChunkX(chunk), CoordinateUtils.getChunkZ(chunk) -- ) != currRegion; -- // Folia end - region threading -+ // Folia start - use area based lock to reduce contention -+ final int chunkX = CoordinateUtils.getChunkX(chunk); -+ final int chunkZ = CoordinateUtils.getChunkZ(chunk); -+ final RegionFileIOThread.ChunkCoordinate chunkCoord = new RegionFileIOThread.ChunkCoordinate(chunk); -+ final Ticket probe = new Ticket<>(type, level, identifier, PROBE_MARKER); - -- this.ticketLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = lock ? this.ticketLockArea.lock(chunkX, chunkZ) : null; - try { -- // Folia start - region threading -- if (lock) { -- // we just need to prevent merging, so we only need the read lock -- // additionally, this will prevent deadlock in the remove all tickets function by using the read lock -- this.world.regioniser.acquireReadLock(); -- } -- try { -- final ChunkHolderManager.HolderManagerRegionData targetData = lock ? this.getDataFor(chunk) : currRegion.getData().getHolderManagerRegionData(); -- // Folia end - region threading -- -- final SortedArraySet> ticketsAtChunk = targetData == null ? null : targetData.tickets.get(chunk); -- // Folia end - region threading -+ final SortedArraySet> ticketsAtChunk = this.tickets.get(chunkCoord); -+ // Folia end - use area based lock to reduce contention - if (ticketsAtChunk == null) { - return false; - } - - final int oldLevel = getTicketLevelAt(ticketsAtChunk); -- final Ticket ticket = (Ticket)ticketsAtChunk.removeAndGet(new Ticket<>(type, level, identifier, PROBE_MARKER)); // Folia - region threading -+ final Ticket ticket = (Ticket)ticketsAtChunk.removeAndGet(probe); // Folia - region threading // Folia - use area based lock to reduce contention - - if (ticket == null) { - return false; - } - -- int newLevel = getTicketLevelAt(ticketsAtChunk); // Folia - region threading - moved up from below -- // Folia start - region threading -+ final int newLevel = getTicketLevelAt(ticketsAtChunk); // Folia - region threading - moved up from below // Folia start - use area based lock to reduce contention -+ // Folia start - use area based lock to reduce contention - // we should not change the ticket levels while the target region may be ticking -- if (newLevel > level) { -- final long unknownRemoveTick = targetData.currentTick + Math.max(0, TicketType.UNKNOWN.timeout); -- final Ticket unknownTicket = new Ticket<>(TicketType.UNKNOWN, level, new ChunkPos(chunk), unknownRemoveTick); -+ if (oldLevel != newLevel) { -+ // we always expect UNKNOWN timeout to be 1, but just in case use max... -+ final Ticket unknownTicket = new Ticket<>(TicketType.UNKNOWN, level, new ChunkPos(chunk), Math.max(1, TicketType.UNKNOWN.timeout)); - if (ticketsAtChunk.add(unknownTicket)) { -- targetData.removeTickToChunkExpireTicketCount.computeIfAbsent(unknownRemoveTick, (final long keyInMap) -> { -- return new Long2IntOpenHashMap(); -- }).addTo(chunk, 1); -+ this.addExpireCount(chunkX, chunkZ); -+ // Folia end - use area based lock to reduce contention - } else { - throw new IllegalStateException("Should have been able to add " + unknownTicket + " to " + ticketsAtChunk); - } -- newLevel = level; - } -+ // Folia end - use area based lock to reduce contention - // Folia end - region threading - -- if (ticketsAtChunk.isEmpty()) { -- targetData.tickets.remove(chunk); // Folia - region threading -- } -+ // Folia - use area based lock to reduce contention - not possible anymore - - // Folia - region threading - move up - -- final long removeTick = ticket.removalTick; -- if (removeTick != NO_TIMEOUT_MARKER) { -- final Long2IntOpenHashMap removeCounts = targetData.removeTickToChunkExpireTicketCount.get(removeTick); // Folia - region threading -- final int currCount = removeCounts.addTo(chunk, -1); -- -- if (currCount == 1) { -- removeCounts.remove(chunk); -- if (removeCounts.isEmpty()) { -- targetData.removeTickToChunkExpireTicketCount.remove(removeTick); // Folia - region threading -- } -- } -+ // Folia start - use area based lock to reduce contention -+ final long removeDelay = ticket.removeDelay; -+ if (removeDelay != NO_TIMEOUT_MARKER) { -+ this.removeExpireCount(chunkX, chunkZ); -+ // Folia end - use area based lock to reduce contention - } - -- if (oldLevel != newLevel) { -- this.updateTicketLevel(chunk, newLevel); -- } -+ // Folia - use area based lock to reduce contention - not possible anymore - - return true; -- } finally { // Folia start - region threading -- if (lock) { -- this.world.regioniser.releaseReadLock(); -- } -- } // Folia end - region threading -+ // Folia - use area based lock to reduce contention - } finally { -- this.ticketLock.unlock(); -+ // Folia start - use area based lock to reduce contention -+ if (ticketLock != null) { -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ // Folia end - use area based lock to reduce contention - } - } - - // atomic with respect to all add/remove/addandremove ticket calls for the given chunk - public void addAndRemoveTickets(final long chunk, final TicketType addType, final int addLevel, final T addIdentifier, - final TicketType removeType, final int removeLevel, final V removeIdentifier) { -- this.ticketLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(CoordinateUtils.getChunkX(chunk), CoordinateUtils.getChunkZ(chunk)); // Folia - use area based lock to reduce contention - try { -- this.addTicketAtLevel(addType, chunk, addLevel, addIdentifier); -- this.removeTicketAtLevel(removeType, chunk, removeLevel, removeIdentifier); -+ // Folia start - use area based lock to reduce contention -+ this.addTicketAtLevel(addType, chunk, addLevel, addIdentifier, false); -+ this.removeTicketAtLevel(removeType, chunk, removeLevel, removeIdentifier, false); -+ // Folia end - use area based lock to reduce contention - } finally { -- this.ticketLock.unlock(); -+ this.ticketLockArea.unlock(ticketLock); // Folia - use area based lock to reduce contention - } - } - - // atomic with respect to all add/remove/addandremove ticket calls for the given chunk - public boolean addIfRemovedTicket(final long chunk, final TicketType addType, final int addLevel, final T addIdentifier, - final TicketType removeType, final int removeLevel, final V removeIdentifier) { -- this.ticketLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(CoordinateUtils.getChunkX(chunk), CoordinateUtils.getChunkZ(chunk)); // Folia - use area based lock to reduce contention - try { -- if (this.removeTicketAtLevel(removeType, chunk, removeLevel, removeIdentifier)) { -- this.addTicketAtLevel(addType, chunk, addLevel, addIdentifier); -+ // Folia start - use area based lock to reduce contention -+ if (this.removeTicketAtLevel(removeType, chunk, removeLevel, removeIdentifier, false)) { -+ this.addTicketAtLevel(addType, chunk, addLevel, addIdentifier, false); -+ // Folia end - use area based lock to reduce contention - return true; - } - return false; - } finally { -- this.ticketLock.unlock(); -+ this.ticketLockArea.unlock(ticketLock); // Folia - use area based lock to reduce contention - } - } - -@@ -791,58 +819,122 @@ public final class ChunkHolderManager { - return; - } - -- this.ticketLock.lock(); -- try { -- // Folia start - region threading -- this.world.regioniser.computeForAllRegions((region) -> { -- for (final LongIterator iterator = new LongArrayList(region.getData().getHolderManagerRegionData().tickets.keySet()).longIterator(); iterator.hasNext();) { -- final long chunk = iterator.nextLong(); -+ // Folia start - use area based lock to reduce contention -+ final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap> sections = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap(); -+ final int sectionShift = ChunkTaskScheduler.getChunkSystemLockShift(); -+ for (final RegionFileIOThread.ChunkCoordinate coord : this.tickets.keySet()) { -+ sections.computeIfAbsent( -+ CoordinateUtils.getChunkKey( -+ CoordinateUtils.getChunkX(coord.key) >> sectionShift, -+ CoordinateUtils.getChunkZ(coord.key) >> sectionShift -+ ), -+ (final long keyInMap) -> { -+ return new ArrayList<>(); -+ } -+ ).add(coord); -+ } -+ -+ for (final Iterator>> iterator = sections.long2ObjectEntrySet().fastIterator(); -+ iterator.hasNext();) { -+ final it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry> entry = iterator.next(); -+ final long sectionKey = entry.getLongKey(); -+ final List coordinates = entry.getValue(); - -- this.removeTicketAtLevel(ticketType, chunk, ticketLevel, ticketIdentifier); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock( -+ CoordinateUtils.getChunkX(sectionKey) << sectionShift, -+ CoordinateUtils.getChunkZ(sectionKey) << sectionShift -+ ); -+ try { -+ for (final RegionFileIOThread.ChunkCoordinate coord : coordinates) { -+ this.removeTicketAtLevel(ticketType, coord.key, ticketLevel, ticketIdentifier, false); - } -- }); -- // Folia end - region threading -- } finally { -- this.ticketLock.unlock(); -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); -+ } - } -+ // Folia end - use area based lock to reduce contention - } - - public void tick() { -- // Folia start - region threading -- final ChunkHolderManager.HolderManagerRegionData data = this.getCurrentRegionData(); -- if (data == null) { -+ // Folia start - use area based lock to reduce contention -+ final ThreadedRegionizer.ThreadedRegion region = -+ TickRegionScheduler.getCurrentRegion(); -+ if (region == null) { - throw new IllegalStateException("Not running tick() while on a region"); - } -- // Folia end - region threading - -- this.ticketLock.lock(); -- try { -- final long tick = ++data.currentTick; // Folia - region threading -+ final int sectionShift = TickRegions.getRegionChunkShift(); -+ -+ final Predicate> expireNow = (final Ticket ticket) -> { -+ if (ticket.removeDelay == NO_TIMEOUT_MARKER) { -+ return false; -+ } -+ return --ticket.removeDelay <= 0L; -+ }; -+ -+ for (final LongIterator iterator = region.getOwnedSectionsUnsynchronised(); iterator.hasNext();) { -+ final long sectionKey = iterator.nextLong(); - -- final Long2IntOpenHashMap toRemove = data.removeTickToChunkExpireTicketCount.remove(tick); // Folia - region threading -+ final RegionFileIOThread.ChunkCoordinate section = new RegionFileIOThread.ChunkCoordinate(sectionKey); - -- if (toRemove == null) { -- return; -+ if (!this.sectionToChunkToExpireCount.containsKey(section)) { -+ continue; - } - -- final Predicate> expireNow = (final Ticket ticket) -> { -- return ticket.removalTick == tick; -- }; -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock( -+ CoordinateUtils.getChunkX(sectionKey) << sectionShift, -+ CoordinateUtils.getChunkZ(sectionKey) << sectionShift -+ ); - -- for (final LongIterator iterator = toRemove.keySet().longIterator(); iterator.hasNext();) { -- final long chunk = iterator.nextLong(); -+ try { -+ final Long2IntOpenHashMap chunkToExpireCount = this.sectionToChunkToExpireCount.get(section); -+ if (chunkToExpireCount == null) { -+ // lost to some race -+ continue; -+ } - -- final SortedArraySet> tickets = data.tickets.get(chunk); // Folia - region threading -- tickets.removeIf(expireNow); -- if (tickets.isEmpty()) { -- data.tickets.remove(chunk); // Folia - region threading -- this.ticketLevelPropagator.removeSource(chunk); -- } else { -- this.ticketLevelPropagator.setSource(chunk, convertBetweenTicketLevels(tickets.first().getTicketLevel())); -+ for (final Iterator iterator1 = chunkToExpireCount.long2IntEntrySet().fastIterator(); iterator1.hasNext();) { -+ final Long2IntMap.Entry entry = iterator1.next(); -+ -+ final long chunkKey = entry.getLongKey(); -+ final int expireCount = entry.getIntValue(); -+ -+ final RegionFileIOThread.ChunkCoordinate chunk = new RegionFileIOThread.ChunkCoordinate(chunkKey); -+ -+ final SortedArraySet> tickets = this.tickets.get(chunk); -+ final int levelBefore = getTicketLevelAt(tickets); -+ -+ final int sizeBefore = tickets.size(); -+ tickets.removeIf(expireNow); -+ final int sizeAfter = tickets.size(); -+ final int levelAfter = getTicketLevelAt(tickets); -+ -+ if (tickets.isEmpty()) { -+ this.tickets.remove(chunk); -+ } -+ if (levelBefore != levelAfter) { -+ this.updateTicketLevel(chunkKey, levelAfter); -+ } -+ -+ final int newExpireCount = expireCount - (sizeBefore - sizeAfter); -+ -+ if (newExpireCount == expireCount) { -+ continue; -+ } -+ -+ if (newExpireCount != 0) { -+ entry.setValue(newExpireCount); -+ } else { -+ iterator1.remove(); -+ } -+ } -+ -+ if (chunkToExpireCount.isEmpty()) { -+ this.sectionToChunkToExpireCount.remove(section); - } -+ } finally { -+ this.ticketLockArea.unlock(ticketLock); - } -- } finally { -- this.ticketLock.unlock(); - } - - this.processTicketUpdates(); -@@ -894,10 +986,11 @@ public final class ChunkHolderManager { - } - - private NewChunkHolder getOrCreateChunkHolder(final long position) { -- if (!this.ticketLock.isHeldByCurrentThread()) { -+ final int chunkX = CoordinateUtils.getChunkX(position); final int chunkZ = CoordinateUtils.getChunkZ(position); // Folia - use area based lock to reduce contention -+ if (!this.ticketLockArea.isHeldByCurrentThread(chunkX, chunkZ)) { // Folia - use area based lock to reduce contention - throw new IllegalStateException("Must hold ticket level update lock!"); - } -- if (!this.taskScheduler.schedulingLock.isHeldByCurrentThread()) { -+ if (!this.taskScheduler.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ)) { // Folia - use area based lock to reduce contention - throw new IllegalStateException("Must hold scheduler lock!!"); - } - -@@ -910,12 +1003,14 @@ public final class ChunkHolderManager { - } - - current = this.createChunkHolder(position); -+ synchronized (this.chunkHolders) { // Folia - use area based lock to reduce contention - this.chunkHolders.put(position, current); -+ } // Folia - use area based lock to reduce contention - - return current; - } - -- private long entityLoadCounter; -+ private final java.util.concurrent.atomic.AtomicLong entityLoadCounter = new java.util.concurrent.atomic.AtomicLong(); // Folia - use area based lock to reduce contention - - public ChunkEntitySlices getOrCreateEntityChunk(final int chunkX, final int chunkZ, final boolean transientChunk) { - TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot create entity chunk off-main"); -@@ -928,13 +1023,13 @@ public final class ChunkHolderManager { - - final AtomicBoolean isCompleted = new AtomicBoolean(); - final Thread waiter = Thread.currentThread(); -- final Long entityLoadId; -+ final Long entityLoadId = Long.valueOf(this.entityLoadCounter.getAndIncrement()); // Folia - use area based lock to reduce contention - NewChunkHolder.GenericDataLoadTaskCallback loadTask = null; -- this.ticketLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(chunkX, chunkZ); // Folia - use area based lock to reduce contention - try { -- entityLoadId = Long.valueOf(this.entityLoadCounter++); -+ // Folia - use area based lock to reduce contention - this.addTicketAtLevel(TicketType.ENTITY_LOAD, chunkX, chunkZ, MAX_TICKET_LEVEL, entityLoadId); -- this.taskScheduler.schedulingLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.taskScheduler.schedulingLockArea.lock(chunkX, chunkZ); // Folia - use area based lock to reduce contention - try { - current = this.getOrCreateChunkHolder(chunkX, chunkZ); - if ((ret = current.getEntityChunk()) != null && (transientChunk || !ret.isTransient())) { -@@ -958,10 +1053,10 @@ public final class ChunkHolderManager { - } - } - } finally { -- this.taskScheduler.schedulingLock.unlock(); -+ this.taskScheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - } finally { -- this.ticketLock.unlock(); -+ this.ticketLockArea.unlock(ticketLock); // Folia - use area based lock to reduce contention - } - - if (loadTask != null) { -@@ -1003,7 +1098,7 @@ public final class ChunkHolderManager { - return null; - } - -- private long poiLoadCounter; -+ private final java.util.concurrent.atomic.AtomicLong poiLoadCounter = new java.util.concurrent.atomic.AtomicLong(); // Folia - use area based lock to reduce contention - - public PoiChunk loadPoiChunk(final int chunkX, final int chunkZ) { - TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Cannot create poi chunk off-main"); -@@ -1020,13 +1115,13 @@ public final class ChunkHolderManager { - final AtomicReference completed = new AtomicReference<>(); - final AtomicBoolean isCompleted = new AtomicBoolean(); - final Thread waiter = Thread.currentThread(); -- final Long poiLoadId; -+ final Long poiLoadId = Long.valueOf(this.poiLoadCounter.getAndIncrement()); - NewChunkHolder.GenericDataLoadTaskCallback loadTask = null; -- this.ticketLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(chunkX, chunkZ); // Folia - use area based lock to reduce contention - try { -- poiLoadId = Long.valueOf(this.poiLoadCounter++); -+ // Folia - use area based lock to reduce contention - this.addTicketAtLevel(TicketType.POI_LOAD, chunkX, chunkZ, MAX_TICKET_LEVEL, poiLoadId); -- this.taskScheduler.schedulingLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.taskScheduler.schedulingLockArea.lock(chunkX, chunkZ); // Folia - use area based lock to reduce contention - try { - current = this.getOrCreateChunkHolder(chunkX, chunkZ); - if (current.isPoiChunkLoaded()) { -@@ -1045,10 +1140,10 @@ public final class ChunkHolderManager { - poiLoad.raisePriority(PrioritisedExecutor.Priority.BLOCKING); - } - } finally { -- this.taskScheduler.schedulingLock.unlock(); -+ this.taskScheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - } finally { -- this.ticketLock.unlock(); -+ this.ticketLockArea.unlock(ticketLock); // Folia - use area based lock to reduce contention - } - - if (loadTask != null) { -@@ -1129,7 +1224,9 @@ public final class ChunkHolderManager { - } - } - -- final ReferenceLinkedOpenHashSet unloadQueue = new ReferenceLinkedOpenHashSet<>(); -+ // Folia start - use area based lock to reduce contention -+ final io.papermc.paper.threadedregions.ChunkQueue unloadQueue; -+ // Folia end - use area based lock to reduce contention - - /* - * Note: Only called on chunk holders that the current ticking region owns -@@ -1140,7 +1237,9 @@ public final class ChunkHolderManager { - // Folia - region threading - ChunkSystem.onChunkHolderDelete(this.world, holder.vanillaChunkHolder); - this.getCurrentRegionData().autoSaveQueue.remove(holder); // Folia - region threading -+ synchronized (this.chunkHolders) { // Folia - use area based lock to reduce contention - this.chunkHolders.remove(CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ)); -+ } // Folia - use area based lock to reduce contention - } - - // note: never call while inside the chunk system, this will absolutely break everything -@@ -1150,100 +1249,150 @@ public final class ChunkHolderManager { - if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { - throw new IllegalStateException("Cannot unload chunks recursively"); - } -- if (this.ticketLock.isHeldByCurrentThread()) { -- throw new IllegalStateException("Cannot hold ticket update lock while calling processUnloads"); -+ // Folia start - use area based lock to reduce contention -+ final int sectionShift = this.unloadQueue.coordinateShift; // sectionShift <= lock shift -+ final List unloadSectionsForRegion = this.unloadQueue.retrieveForCurrentRegion(); -+ int unloadCountTentative = 0; -+ for (final io.papermc.paper.threadedregions.ChunkQueue.SectionToUnload sectionRef : unloadSectionsForRegion) { -+ final io.papermc.paper.threadedregions.ChunkQueue.UnloadSection section -+ = this.unloadQueue.getSectionUnsynchronized(sectionRef.sectionX(), sectionRef.sectionZ()); -+ -+ if (section == null) { -+ // removed concurrently -+ continue; -+ } -+ -+ // technically reading the size field is unsafe, and it may be incorrect. -+ // We assume that the error here cumulatively goes away over many ticks. If it did not, then it is possible -+ // for chunks to never unload or not unload fast enough. -+ unloadCountTentative += section.chunks.size(); - } -- if (this.taskScheduler.schedulingLock.isHeldByCurrentThread()) { -- throw new IllegalStateException("Cannot hold scheduling lock while calling processUnloads"); -+ -+ if (unloadCountTentative <= 0) { -+ // no work to do -+ return; - } - -- final ChunkHolderManager.HolderManagerRegionData currentData = this.getCurrentRegionData(); // Folia - region threading -+ // Note: The behaviour that we process ticket updates while holding the lock has been dropped here, as it is racey behavior. -+ // But, we do need to process updates here so that any add ticket that is synchronised before this call does not go missed. -+ this.processTicketUpdates(); - -- final List unloadQueue; -- final List scheduleList = new ArrayList<>(); -- this.ticketLock.lock(); -- try { -- this.taskScheduler.schedulingLock.lock(); -+ final int toUnloadCount = Math.max(50, (int)(unloadCountTentative * 0.05)); -+ int processedCount = 0; -+ -+ for (final io.papermc.paper.threadedregions.ChunkQueue.SectionToUnload sectionRef : unloadSectionsForRegion) { -+ final List stage1 = new ArrayList<>(); -+ final List stage2 = new ArrayList<>(); -+ -+ final int sectionLowerX = sectionRef.sectionX() << sectionShift; -+ final int sectionLowerZ = sectionRef.sectionZ() << sectionShift; -+ -+ // stage 1: set up for stage 2 while holding critical locks -+ ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.ticketLockArea.lock(sectionLowerX, sectionLowerZ); - try { -- if (this.unloadQueue.isEmpty()) { -- return; -- } -- // in order to ensure all chunks in the unload queue do not have a pending ticket level update, -- // process them now -- this.processTicketUpdates(false, false, scheduleList); -- -- // Folia start - region threading -- final ArrayDeque toUnload = new ArrayDeque<>(); -- // The unload queue is globally maintained, but we can only unload chunks in our region -- for (final NewChunkHolder holder : this.unloadQueue) { -- if (TickThread.isTickThreadFor(this.world, holder.chunkX, holder.chunkZ)) { -- toUnload.add(holder); -- } -- } -- // Folia end - region threading -- -- final int unloadCount = Math.max(50, (int)(toUnload.size() * 0.05)); // Folia - region threading -- unloadQueue = new ArrayList<>(unloadCount + 1); // Folia - region threading -- for (int i = 0; i < unloadCount && !toUnload.isEmpty(); ++i) { // Folia - region threading -- final NewChunkHolder chunkHolder = toUnload.removeFirst(); // Folia - region threading -- this.unloadQueue.remove(chunkHolder); // Folia - region threading -- if (chunkHolder.isSafeToUnload() != null) { -- LOGGER.error("Chunkholder " + chunkHolder + " is not safe to unload but is inside the unload queue?"); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node scheduleLock = this.taskScheduler.schedulingLockArea.lock(sectionLowerX, sectionLowerZ); -+ try { -+ final io.papermc.paper.threadedregions.ChunkQueue.UnloadSection section -+ = this.unloadQueue.getSectionUnsynchronized(sectionRef.sectionX(), sectionRef.sectionZ()); -+ -+ if (section == null) { -+ // removed concurrently - continue; - } -- final NewChunkHolder.UnloadState state = chunkHolder.unloadStage1(); -- if (state == null) { -- // can unload immediately -- this.removeChunkHolder(chunkHolder); -- continue; -+ -+ // collect the holders to run stage 1 on -+ final int sectionCount = section.chunks.size(); -+ -+ if ((sectionCount + processedCount) <= toUnloadCount) { -+ // we can just drain the entire section -+ -+ for (final LongIterator iterator = section.chunks.iterator(); iterator.hasNext();) { -+ final NewChunkHolder holder = this.chunkHolders.get(iterator.nextLong()); -+ if (holder == null) { -+ throw new IllegalStateException(); -+ } -+ stage1.add(holder); -+ } -+ -+ // remove section -+ this.unloadQueue.removeSection(sectionRef.sectionX(), sectionRef.sectionZ()); -+ } else { -+ // processedCount + len = toUnloadCount -+ // we cannot drain the entire section -+ for (int i = 0, len = toUnloadCount - processedCount; i < len; ++i) { -+ final NewChunkHolder holder = this.chunkHolders.get(section.chunks.removeFirstLong()); -+ if (holder == null) { -+ throw new IllegalStateException(); -+ } -+ stage1.add(holder); -+ } - } -- unloadQueue.add(state); -+ -+ // run stage 1 -+ for (int i = 0, len = stage1.size(); i < len; ++i) { -+ final NewChunkHolder chunkHolder = stage1.get(i); -+ if (chunkHolder.isSafeToUnload() != null) { -+ LOGGER.error("Chunkholder " + chunkHolder + " is not safe to unload but is inside the unload queue?"); -+ continue; -+ } -+ final NewChunkHolder.UnloadState state = chunkHolder.unloadStage1(); -+ if (state == null) { -+ // can unload immediately -+ this.removeChunkHolder(chunkHolder); -+ continue; -+ } -+ stage2.add(state); -+ } -+ } finally { -+ this.taskScheduler.schedulingLockArea.unlock(scheduleLock); - } - } finally { -- this.taskScheduler.schedulingLock.unlock(); -+ this.ticketLockArea.unlock(ticketLock); - } -- } finally { -- this.ticketLock.unlock(); -- } -- // schedule tasks, we can't let processTicketUpdates do this because we call it holding the schedule lock -- for (int i = 0, len = scheduleList.size(); i < len; ++i) { -- scheduleList.get(i).schedule(); -- } - -- final List toRemove = new ArrayList<>(unloadQueue.size()); -+ // stage 2: invoke expensive unload logic, designed to run without locks thanks to stage 1 -+ final List stage3 = new ArrayList<>(stage2.size()); - -- final Boolean before = this.blockTicketUpdates(); -- try { -- for (int i = 0, len = unloadQueue.size(); i < len; ++i) { -- final NewChunkHolder.UnloadState state = unloadQueue.get(i); -- final NewChunkHolder holder = state.holder(); -+ final Boolean before = this.blockTicketUpdates(); -+ try { -+ for (int i = 0, len = stage2.size(); i < len; ++i) { -+ final NewChunkHolder.UnloadState state = stage2.get(i); -+ final NewChunkHolder holder = state.holder(); - -- holder.unloadStage2(state); -- toRemove.add(holder); -+ holder.unloadStage2(state); -+ stage3.add(holder); -+ } -+ } finally { -+ this.unblockTicketUpdates(before); - } -- } finally { -- this.unblockTicketUpdates(before); -- } - -- this.ticketLock.lock(); -- try { -- this.taskScheduler.schedulingLock.lock(); -+ // stage 3: actually attempt to remove the chunk holders -+ ticketLock = this.ticketLockArea.lock(sectionLowerX, sectionLowerZ); - try { -- for (int i = 0, len = toRemove.size(); i < len; ++i) { -- final NewChunkHolder holder = toRemove.get(i); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node scheduleLock = this.taskScheduler.schedulingLockArea.lock(sectionLowerX, sectionLowerZ); -+ try { -+ for (int i = 0, len = stage3.size(); i < len; ++i) { -+ final NewChunkHolder holder = stage3.get(i); - -- if (holder.unloadStage3()) { -- this.removeChunkHolder(holder); -- } else { -- // add cooldown so the next unload check is not immediately next tick -- this.addTicketAtLevel(TicketType.UNLOAD_COOLDOWN, holder.chunkX, holder.chunkZ, MAX_TICKET_LEVEL, Unit.INSTANCE); -+ if (holder.unloadStage3()) { -+ this.removeChunkHolder(holder); -+ } else { -+ // add cooldown so the next unload check is not immediately next tick -+ this.addTicketAtLevel(TicketType.UNLOAD_COOLDOWN, CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ), MAX_TICKET_LEVEL, Unit.INSTANCE, false); -+ } - } -+ } finally { -+ this.taskScheduler.schedulingLockArea.unlock(scheduleLock); - } - } finally { -- this.taskScheduler.schedulingLock.unlock(); -+ this.ticketLockArea.unlock(ticketLock); -+ } -+ -+ processedCount += stage1.size(); -+ -+ if (processedCount >= toUnloadCount) { -+ break; - } -- } finally { -- this.ticketLock.unlock(); - } - } - -@@ -1305,88 +1454,71 @@ public final class ChunkHolderManager { - } - } - -- private final MultiThreadedQueue> delayedTicketUpdates = new MultiThreadedQueue<>(); -+ // Folia - use area based lock to reduce contention - -- // note: MUST hold ticket lock, otherwise operation ordering is lost -- private boolean drainTicketUpdates() { -- boolean ret = false; - -- TicketOperation operation; -- while ((operation = this.delayedTicketUpdates.poll()) != null) { -- switch (operation.op) { -- case ADD: { -- ret |= this.addTicketAtLevel(operation.ticketType, operation.chunkCoord, operation.ticketLevel, operation.identifier); -- break; -- } -- case REMOVE: { -- ret |= this.removeTicketAtLevel(operation.ticketType, operation.chunkCoord, operation.ticketLevel, operation.identifier); -- break; -- } -- case ADD_IF_REMOVED: { -- ret |= this.addIfRemovedTicket( -- operation.chunkCoord, -- operation.ticketType, operation.ticketLevel, operation.identifier, -- operation.ticketType2, operation.ticketLevel2, operation.identifier2 -- ); -- break; -- } -- case ADD_AND_REMOVE: { -- ret = true; -- this.addAndRemoveTickets( -- operation.chunkCoord, -- operation.ticketType, operation.ticketLevel, operation.identifier, -- operation.ticketType2, operation.ticketLevel2, operation.identifier2 -- ); -- break; -- } -+ // Folia start - use area based lock to reduce contention -+ private boolean processTicketOp(TicketOperation operation) { -+ boolean ret = false; -+ switch (operation.op) { -+ case ADD: { -+ ret |= this.addTicketAtLevel(operation.ticketType, operation.chunkCoord, operation.ticketLevel, operation.identifier); -+ break; -+ } -+ case REMOVE: { -+ ret |= this.removeTicketAtLevel(operation.ticketType, operation.chunkCoord, operation.ticketLevel, operation.identifier); -+ break; -+ } -+ case ADD_IF_REMOVED: { -+ ret |= this.addIfRemovedTicket( -+ operation.chunkCoord, -+ operation.ticketType, operation.ticketLevel, operation.identifier, -+ operation.ticketType2, operation.ticketLevel2, operation.identifier2 -+ ); -+ break; -+ } -+ case ADD_AND_REMOVE: { -+ ret = true; -+ this.addAndRemoveTickets( -+ operation.chunkCoord, -+ operation.ticketType, operation.ticketLevel, operation.identifier, -+ operation.ticketType2, operation.ticketLevel2, operation.identifier2 -+ ); -+ break; - } - } - - return ret; - } -+ // Folia end - use area based lock to reduce contention - -- public Boolean tryDrainTicketUpdates() { -+ // note: MUST hold ticket lock, otherwise operation ordering is lost -+ private boolean drainTicketUpdates() { - boolean ret = false; -- for (;;) { -- final boolean acquired = this.ticketLock.tryLock(); -- try { -- if (!acquired) { -- return ret ? Boolean.TRUE : null; -- } - -- ret |= this.drainTicketUpdates(); -- } finally { -- if (acquired) { -- this.ticketLock.unlock(); -- } -- } -- if (this.delayedTicketUpdates.isEmpty()) { -- return Boolean.valueOf(ret); -- } // else: try to re-acquire -- } -+ // Folia - use area based lock to reduce contention -+ -+ return ret; -+ } -+ -+ public Boolean tryDrainTicketUpdates() { -+ return Boolean.FALSE; // Folia start - use area based lock to reduce contention - } - - public void pushDelayedTicketUpdate(final TicketOperation operation) { -- this.delayedTicketUpdates.add(operation); -+ this.processTicketOp(operation); // Folia - use area based lock to reduce contention - } - - public void pushDelayedTicketUpdates(final Collection> operations) { -- this.delayedTicketUpdates.addAll(operations); -+ // Folia start - use area based lock to reduce contention -+ for (final TicketOperation operation : operations) { -+ this.processTicketOp(operation); -+ } -+ // Folia end - use area based lock to reduce contention - } - - public Boolean tryProcessTicketUpdates() { -- final boolean acquired = this.ticketLock.tryLock(); -- try { -- if (!acquired) { -- return null; -- } -- -- return Boolean.valueOf(this.processTicketUpdates(false, true, null)); -- } finally { -- if (acquired) { -- this.ticketLock.unlock(); -- } -- } -+ return Boolean.valueOf(this.processTicketUpdates()); // Folia - use area based lock to reduce contention - } - - private final ThreadLocal BLOCK_TICKET_UPDATES = ThreadLocal.withInitial(() -> { -@@ -1420,12 +1552,7 @@ public final class ChunkHolderManager { - if (BLOCK_TICKET_UPDATES.get() == Boolean.TRUE) { - throw new IllegalStateException("Cannot update ticket level while unloading chunks or updating entity manager"); - } -- if (checkLocks && this.ticketLock.isHeldByCurrentThread()) { -- throw new IllegalStateException("Illegal recursive processTicketUpdates!"); -- } -- if (checkLocks && this.taskScheduler.schedulingLock.isHeldByCurrentThread()) { -- throw new IllegalStateException("Cannot update ticket levels from a scheduler context!"); -- } -+ // Folia - use area based lock to reduce contention - - List changedFullStatus = null; - -@@ -1435,94 +1562,19 @@ public final class ChunkHolderManager { - final boolean canProcessFullUpdates = processFullUpdates & isTickThread; - final boolean canProcessScheduling = scheduledTasks == null; - -- this.ticketLock.lock(); -- try { -- this.drainTicketUpdates(); -- -- final boolean levelsUpdated = this.ticketLevelPropagator.propagateUpdates(); -- if (levelsUpdated) { -- // Unlike CB, ticket level updates cannot happen recursively. Thank god. -- if (!this.ticketLevelUpdates.isEmpty()) { -- ret = true; -- -- // first the necessary chunkholders must be created, so just update the ticket levels -- for (final Iterator iterator = this.ticketLevelUpdates.long2IntEntrySet().fastIterator(); iterator.hasNext();) { -- final Long2IntMap.Entry entry = iterator.next(); -- final long key = entry.getLongKey(); -- final int newLevel = entry.getIntValue(); -- -- NewChunkHolder current = this.chunkHolders.get(key); -- if (current == null && newLevel > MAX_TICKET_LEVEL) { -- // not loaded and it shouldn't be loaded! -- iterator.remove(); -- continue; -- } -- -- final int currentLevel = current == null ? MAX_TICKET_LEVEL + 1 : current.getCurrentTicketLevel(); -- if (currentLevel == newLevel) { -- // nothing to do -- iterator.remove(); -- continue; -- } -- -- if (current == null) { -- // must create -- current = this.createChunkHolder(key); -- this.chunkHolders.put(key, current); -- current.updateTicketLevel(newLevel); -- } else { -- current.updateTicketLevel(newLevel); -- } -- } -- -- if (scheduledTasks == null) { -- scheduledTasks = new ArrayList<>(); -- } -- changedFullStatus = new ArrayList<>(); -- -- // allow the chunkholders to process ticket level updates without needing to acquire the schedule lock every time -- final List prev = CURRENT_TICKET_UPDATE_SCHEDULING.get(); -- CURRENT_TICKET_UPDATE_SCHEDULING.set(scheduledTasks); -- try { -- this.taskScheduler.schedulingLock.lock(); -- try { -- for (final Iterator iterator = this.ticketLevelUpdates.long2IntEntrySet().fastIterator(); iterator.hasNext();) { -- final Long2IntMap.Entry entry = iterator.next(); -- final long key = entry.getLongKey(); -- final NewChunkHolder current = this.chunkHolders.get(key); -- -- if (current == null) { -- throw new IllegalStateException("Expected chunk holder to be created"); -- } -- -- current.processTicketLevelUpdate(scheduledTasks, changedFullStatus); -- } -- } finally { -- this.taskScheduler.schedulingLock.unlock(); -- } -- } finally { -- CURRENT_TICKET_UPDATE_SCHEDULING.set(prev); -- } -- -- this.ticketLevelUpdates.clear(); -- } -+ // Folia start - use area based lock to reduce contention -+ if (this.ticketLevelPropagator.hasPendingUpdates()) { -+ if (scheduledTasks == null) { -+ scheduledTasks = new ArrayList<>(); - } -+ changedFullStatus = new ArrayList<>(); - -- // Folia start - region threading -- // it is possible that a special case new chunk holder had its ticket removed before it was propagated, -- // which means checkUnload was never invoked. By checking unload here, we ensure that either the -- // ticket level was propagated (in which case, a later depropagation would check again) or that -- // we called checkUnload for it. -- if (!this.specialCaseUnload.isEmpty()) { -- for (final NewChunkHolder special : this.specialCaseUnload) { -- special.checkUnload(); -- } -- this.specialCaseUnload.clear(); -- } -- // Folia end - region threading -- } finally { -- this.ticketLock.unlock(); -+ ret |= this.ticketLevelPropagator.performUpdates( -+ this.ticketLockArea, this.taskScheduler.schedulingLockArea, -+ scheduledTasks, changedFullStatus -+ ); - } -+ // Folia end - use area based lock to reduce contention - - if (changedFullStatus != null) { - this.addChangedStatuses(changedFullStatus); -@@ -1568,43 +1620,7 @@ public final class ChunkHolderManager { - } - - public JsonObject getDebugJsonForWatchdog() { -- // try and detect any potential deadlock that would require us to read unlocked -- try { -- if (this.ticketLock.tryLock(10, TimeUnit.SECONDS)) { -- try { -- if (this.taskScheduler.schedulingLock.tryLock(10, TimeUnit.SECONDS)) { -- try { -- return this.getDebugJsonNoLock(); -- } finally { -- this.taskScheduler.schedulingLock.unlock(); -- } -- } -- } finally { -- this.ticketLock.unlock(); -- } -- } -- } catch (final InterruptedException ignore) {} -- -- LOGGER.error("Failed to acquire ticket and scheduling lock before timeout for world " + this.world.getWorld().getName()); -- -- // because we read without locks, it may throw exceptions for fastutil maps -- // so just try until it works... -- Throwable lastException = null; -- for (int count = 0;count < 1000;++count) { -- try { -- return this.getDebugJsonNoLock(); -- } catch (final ThreadDeath death) { -- throw death; -- } catch (final Throwable thr) { -- lastException = thr; -- Thread.yield(); -- LockSupport.parkNanos(10_000L); -- } -- } -- -- // failed, return -- LOGGER.error("Failed to retrieve debug json for watchdog thread without locking", lastException); -- return null; -+ return this.getDebugJsonNoLock(); // Folia - use area based lock to reduce contention - } - - private JsonObject getDebugJsonNoLock() { -@@ -1613,12 +1629,31 @@ public final class ChunkHolderManager { - - final JsonArray unloadQueue = new JsonArray(); - ret.add("unload_queue", unloadQueue); -- for (final NewChunkHolder holder : this.unloadQueue) { -- final JsonObject coordinate = new JsonObject(); -- unloadQueue.add(coordinate); -+ // Folia start - use area based lock to reduce contention -+ ret.addProperty("lock_shift", Integer.valueOf(ChunkTaskScheduler.getChunkSystemLockShift())); -+ ret.addProperty("ticket_shift", Integer.valueOf(io.papermc.paper.threadedregions.ThreadedTicketLevelPropagator.SECTION_SHIFT)); -+ ret.addProperty("region_shift", Integer.valueOf(this.world.regioniser.sectionChunkShift)); -+ for (final io.papermc.paper.threadedregions.ChunkQueue.SectionToUnload section : this.unloadQueue.retrieveForAllRegions()) { -+ final JsonObject sectionJson = new JsonObject(); -+ unloadQueue.add(sectionJson); -+ sectionJson.addProperty("sectionX", section.sectionX()); -+ sectionJson.addProperty("sectionZ", section.sectionX()); -+ sectionJson.addProperty("order", section.order()); -+ -+ final JsonArray coordinates = new JsonArray(); -+ sectionJson.add("coordinates", coordinates); -+ -+ final io.papermc.paper.threadedregions.ChunkQueue.UnloadSection actualSection = this.unloadQueue.getSectionUnsynchronized(section.sectionX(), section.sectionZ()); -+ for (final LongIterator iterator = actualSection.chunks.iterator(); iterator.hasNext();) { -+ final long coordinate = iterator.nextLong(); -+ -+ final JsonObject coordinateJson = new JsonObject(); -+ coordinates.add(coordinateJson); - -- coordinate.addProperty("chunkX", Integer.valueOf(holder.chunkX)); -- coordinate.addProperty("chunkZ", Integer.valueOf(holder.chunkZ)); -+ coordinateJson.addProperty("chunkX", Integer.valueOf(CoordinateUtils.getChunkX(coordinate))); -+ coordinateJson.addProperty("chunkZ", Integer.valueOf(CoordinateUtils.getChunkZ(coordinate))); -+ } -+ // Folia end - use area based lock to reduce contention - } - - final JsonArray holders = new JsonArray(); -@@ -1628,71 +1663,77 @@ public final class ChunkHolderManager { - holders.add(holder.getDebugJson()); - } - -- // Folia start - region threading -- final JsonArray regions = new JsonArray(); -- ret.add("regions", regions); -- this.world.regioniser.computeForAllRegionsUnsynchronised((region) -> { -- final JsonObject regionJson = new JsonObject(); -- regions.add(regionJson); -+ // Folia start - use area based lock to reduce contention -+ // TODO -+ /* -+ final JsonArray removeTickToChunkExpireTicketCount = new JsonArray(); -+ regionJson.add("remove_tick_to_chunk_expire_ticket_count", removeTickToChunkExpireTicketCount); - -- final TickRegions.TickRegionData regionData = region.getData(); -+ for (final Long2ObjectMap.Entry tickEntry : regionData.getHolderManagerRegionData().removeTickToChunkExpireTicketCount.long2ObjectEntrySet()) { -+ final long tick = tickEntry.getLongKey(); -+ final Long2IntOpenHashMap coordinateToCount = tickEntry.getValue(); - -- regionJson.addProperty("current_tick", Long.valueOf(regionData.getCurrentTick())); -+ final JsonObject tickJson = new JsonObject(); -+ removeTickToChunkExpireTicketCount.add(tickJson); - -- final JsonArray removeTickToChunkExpireTicketCount = new JsonArray(); -- regionJson.add("remove_tick_to_chunk_expire_ticket_count", removeTickToChunkExpireTicketCount); -+ tickJson.addProperty("tick", Long.valueOf(tick)); - -- for (final Long2ObjectMap.Entry tickEntry : regionData.getHolderManagerRegionData().removeTickToChunkExpireTicketCount.long2ObjectEntrySet()) { -- final long tick = tickEntry.getLongKey(); -- final Long2IntOpenHashMap coordinateToCount = tickEntry.getValue(); -+ final JsonArray tickEntries = new JsonArray(); -+ tickJson.add("entries", tickEntries); - -- final JsonObject tickJson = new JsonObject(); -- removeTickToChunkExpireTicketCount.add(tickJson); -+ for (final Long2IntMap.Entry entry : coordinateToCount.long2IntEntrySet()) { -+ final long coordinate = entry.getLongKey(); -+ final int count = entry.getIntValue(); - -- tickJson.addProperty("tick", Long.valueOf(tick)); -+ final JsonObject entryJson = new JsonObject(); -+ tickEntries.add(entryJson); - -- final JsonArray tickEntries = new JsonArray(); -- tickJson.add("entries", tickEntries); -+ entryJson.addProperty("chunkX", Long.valueOf(CoordinateUtils.getChunkX(coordinate))); -+ entryJson.addProperty("chunkZ", Long.valueOf(CoordinateUtils.getChunkZ(coordinate))); -+ entryJson.addProperty("count", Integer.valueOf(count)); -+ } -+ } - -- for (final Long2IntMap.Entry entry : coordinateToCount.long2IntEntrySet()) { -- final long coordinate = entry.getLongKey(); -- final int count = entry.getIntValue(); -+ final JsonArray allTicketsJson = new JsonArray(); -+ regionJson.add("tickets", allTicketsJson); - -- final JsonObject entryJson = new JsonObject(); -- tickEntries.add(entryJson); -+ for (final Long2ObjectMap.Entry>> coordinateTickets : regionData.getHolderManagerRegionData().tickets.long2ObjectEntrySet()) { -+ final long coordinate = coordinateTickets.getLongKey(); -+ final SortedArraySet> tickets = coordinateTickets.getValue(); - -- entryJson.addProperty("chunkX", Long.valueOf(CoordinateUtils.getChunkX(coordinate))); -- entryJson.addProperty("chunkZ", Long.valueOf(CoordinateUtils.getChunkZ(coordinate))); -- entryJson.addProperty("count", Integer.valueOf(count)); -- } -- } -+ final JsonObject coordinateJson = new JsonObject(); -+ allTicketsJson.add(coordinateJson); - -- final JsonArray allTicketsJson = new JsonArray(); -- regionJson.add("tickets", allTicketsJson); -+ coordinateJson.addProperty("chunkX", Long.valueOf(CoordinateUtils.getChunkX(coordinate))); -+ coordinateJson.addProperty("chunkZ", Long.valueOf(CoordinateUtils.getChunkZ(coordinate))); - -- for (final Long2ObjectMap.Entry>> coordinateTickets : regionData.getHolderManagerRegionData().tickets.long2ObjectEntrySet()) { -- final long coordinate = coordinateTickets.getLongKey(); -- final SortedArraySet> tickets = coordinateTickets.getValue(); -+ final JsonArray ticketsSerialized = new JsonArray(); -+ coordinateJson.add("tickets", ticketsSerialized); - -- final JsonObject coordinateJson = new JsonObject(); -- allTicketsJson.add(coordinateJson); -+ for (final Ticket ticket : tickets) { -+ final JsonObject ticketSerialized = new JsonObject(); -+ ticketsSerialized.add(ticketSerialized); - -- coordinateJson.addProperty("chunkX", Long.valueOf(CoordinateUtils.getChunkX(coordinate))); -- coordinateJson.addProperty("chunkZ", Long.valueOf(CoordinateUtils.getChunkZ(coordinate))); -+ ticketSerialized.addProperty("type", ticket.getType().toString()); -+ ticketSerialized.addProperty("level", Integer.valueOf(ticket.getTicketLevel())); -+ ticketSerialized.addProperty("identifier", Objects.toString(ticket.key)); -+ ticketSerialized.addProperty("remove_tick", Long.valueOf(ticket.removalTick)); -+ } -+ } -+ */ -+ // Folia end - use area based lock to reduce contention - -- final JsonArray ticketsSerialized = new JsonArray(); -- coordinateJson.add("tickets", ticketsSerialized); -+ // Folia start - region threading -+ final JsonArray regions = new JsonArray(); -+ ret.add("regions", regions); -+ this.world.regioniser.computeForAllRegionsUnsynchronised((region) -> { -+ final JsonObject regionJson = new JsonObject(); -+ regions.add(regionJson); - -- for (final Ticket ticket : tickets) { -- final JsonObject ticketSerialized = new JsonObject(); -- ticketsSerialized.add(ticketSerialized); -+ final TickRegions.TickRegionData regionData = region.getData(); - -- ticketSerialized.addProperty("type", ticket.getType().toString()); -- ticketSerialized.addProperty("level", Integer.valueOf(ticket.getTicketLevel())); -- ticketSerialized.addProperty("identifier", Objects.toString(ticket.key)); -- ticketSerialized.addProperty("remove_tick", Long.valueOf(ticket.removalTick)); -- } -- } -+ regionJson.addProperty("current_tick", Long.valueOf(regionData.getCurrentTick())); -+ // Folia - use area based lock to reduce contention - }); - // Folia end - region threading - -@@ -1700,27 +1741,6 @@ public final class ChunkHolderManager { - } - - public JsonObject getDebugJson() { -- final List scheduleList = new ArrayList<>(); -- try { -- final JsonObject ret; -- this.ticketLock.lock(); -- try { -- this.taskScheduler.schedulingLock.lock(); -- try { -- this.processTicketUpdates(false, false, scheduleList); -- ret = this.getDebugJsonNoLock(); -- } finally { -- this.taskScheduler.schedulingLock.unlock(); -- } -- } finally { -- this.ticketLock.unlock(); -- } -- return ret; -- } finally { -- // schedule tasks, we can't let processTicketUpdates do this because we call it holding the schedule lock -- for (int i = 0, len = scheduleList.size(); i < len; ++i) { -- scheduleList.get(i).schedule(); -- } -- } -+ return this.getDebugJsonNoLock(); // Folia - use area based lock to reduce contention - } - } -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java -index 1f7c146ff0b2a835c818f49da6c1f1411f26aa39..7b8362625b48f1829ed4fd3c7fde6a4bec8e4099 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java -@@ -82,7 +82,7 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - - // NOTE: it is IMPOSSIBLE for getOrLoadEntityData/getOrLoadPoiData to complete synchronously, because - // they must schedule a task to off main or to on main to complete -- this.scheduler.schedulingLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); // Folia - use area based lock to reduce contention - try { - if (this.scheduled) { - throw new IllegalStateException("schedule() called twice"); -@@ -108,7 +108,7 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - this.entityLoadTask = entityLoadTask; - this.poiLoadTask = poiLoadTask; - } finally { -- this.scheduler.schedulingLock.unlock(); -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - - if (entityLoadTask != null) { -@@ -125,11 +125,11 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - @Override - public void cancel() { - // must be before load task access, so we can synchronise with the writes to the fields -- this.scheduler.schedulingLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); // Folia - use area based lock to reduce contention - try { - this.cancelled = true; - } finally { -- this.scheduler.schedulingLock.unlock(); -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - - /* -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java -index 5ca4e9c85c957c669d54fd9e5e52f13502b592da..4ea4b5a78fe629d5aa565d8d9a47f58d96084c0c 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java -@@ -117,7 +117,7 @@ public final class ChunkTaskScheduler { - - // Folia - regionised ticking - -- final ReentrantLock schedulingLock = new ReentrantLock(); -+ // Folia - use area based lock to reduce contention - replaced by schedulingLockArea - public final ChunkHolderManager chunkHolderManager; - - static { -@@ -193,6 +193,73 @@ public final class ChunkTaskScheduler { - } - } - -+ // Folia start - use area based lock to reduce contention -+ // must be >= region shift and must be >= ticket propagator section shift -+ // it must be >= region shift since the regioniser assumes ticket updates do not occur in parallel for the region sections -+ // it must be >= ticket propagator section shift so that the ticket propagator can assume that owning a position implies owning -+ // the entire section -+ // we just take the max, as we want the smallest shift that satifies these properties -+ private static final int LOCK_SHIFT = Math.max(io.papermc.paper.threadedregions.ThreadedTicketLevelPropagator.SECTION_SHIFT, io.papermc.paper.threadedregions.TickRegions.getRegionChunkShift()); -+ public static int getChunkSystemLockShift() { -+ return LOCK_SHIFT; -+ } -+ -+ private static final int[] ACCESS_RADIUS_TABLE = new int[ChunkStatus.getStatusList().size()]; -+ private static final int[] MAX_ACCESS_RADIUS_TABLE = new int[ACCESS_RADIUS_TABLE.length]; -+ static { -+ Arrays.fill(ACCESS_RADIUS_TABLE, -1); -+ } -+ -+ private static int getAccessRadius0(final ChunkStatus genStatus) { -+ if (genStatus == ChunkStatus.EMPTY) { -+ return 0; -+ } -+ -+ final int radius = Math.max(genStatus.loadRange, genStatus.getRange()); -+ int maxRange = radius; -+ -+ for (int dist = 1; dist <= radius; ++dist) { -+ final ChunkStatus requiredNeighbourStatus = ChunkMap.getDependencyStatus(genStatus, radius); -+ final int rad = ACCESS_RADIUS_TABLE[requiredNeighbourStatus.getIndex()]; -+ if (rad == -1) { -+ throw new IllegalStateException(); -+ } -+ -+ maxRange = Math.max(maxRange, dist + rad); -+ } -+ -+ return maxRange; -+ } -+ -+ private static int maxAccessRadius; -+ -+ static { -+ final List statuses = ChunkStatus.getStatusList(); -+ for (int i = 0, len = statuses.size(); i < len; ++i) { -+ ACCESS_RADIUS_TABLE[i] = getAccessRadius0(statuses.get(i)); -+ } -+ int max = 0; -+ for (int i = 0, len = statuses.size(); i < len; ++i) { -+ MAX_ACCESS_RADIUS_TABLE[i] = max = Math.max(ACCESS_RADIUS_TABLE[i], max); -+ } -+ maxAccessRadius = max; -+ } -+ -+ public static int getMaxAccessRadius() { -+ return maxAccessRadius; -+ } -+ -+ public static int getAccessRadius(final ChunkStatus genStatus) { -+ return ACCESS_RADIUS_TABLE[genStatus.getIndex()]; -+ } -+ -+ public static int getAccessRadius(final ChunkHolder.FullChunkStatus status) { -+ return (status.ordinal() - 1) + getAccessRadius(ChunkStatus.FULL); -+ } -+ -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock schedulingLockArea = new ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock(getChunkSystemLockShift()); -+ // Folia end - use area based lock to reduce contention -+ - public ChunkTaskScheduler(final ServerLevel world, final PrioritisedThreadPool workers) { - this.world = world; - this.workers = workers; -@@ -273,10 +340,11 @@ public final class ChunkTaskScheduler { - }, priority); - return; - } -- if (this.chunkHolderManager.ticketLock.isHeldByCurrentThread()) { -+ final int accessRadius = getAccessRadius(toStatus); // Folia - use area based lock to reduce contention -+ if (this.chunkHolderManager.ticketLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) { // Folia - use area based lock to reduce contention - throw new IllegalStateException("Cannot schedule chunk load during ticket level update"); - } -- if (this.schedulingLock.isHeldByCurrentThread()) { -+ if (this.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) { // Folia - use area based lock to reduce contention - throw new IllegalStateException("Cannot schedule chunk loading recursively"); - } - -@@ -310,9 +378,9 @@ public final class ChunkTaskScheduler { - - final boolean scheduled; - final LevelChunk chunk; -- this.chunkHolderManager.ticketLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ, accessRadius); // Folia - use area based lock to reduce contention - try { -- this.schedulingLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.schedulingLockArea.lock(chunkX, chunkZ, accessRadius); // Folia - use area based lock to reduce contention - try { - final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkKey); - if (chunkHolder == null || chunkHolder.getTicketLevel() > minLevel) { -@@ -343,10 +411,10 @@ public final class ChunkTaskScheduler { - } - } - } finally { -- this.schedulingLock.unlock(); -+ this.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - } finally { -- this.chunkHolderManager.ticketLock.unlock(); -+ this.chunkHolderManager.ticketLockArea.unlock(ticketLock); // Folia - use area based lock to reduce contention - } - - if (!scheduled) { -@@ -384,12 +452,13 @@ public final class ChunkTaskScheduler { - // only appropriate to use with ServerLevel#syncLoadNonFull - public boolean beginChunkLoadForNonFullSync(final int chunkX, final int chunkZ, final ChunkStatus toStatus, - final PrioritisedExecutor.Priority priority) { -+ final int accessRadius = getAccessRadius(toStatus); // Folia - use area based lock to reduce contention - final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); - final int minLevel = 33 + ChunkStatus.getDistance(toStatus); - final List tasks = new ArrayList<>(); -- this.chunkHolderManager.ticketLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ, accessRadius); // Folia - use area based lock to reduce contention - try { -- this.schedulingLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.schedulingLockArea.lock(chunkX, chunkZ, accessRadius); // Folia - use area based lock to reduce contention - try { - final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkKey); - if (chunkHolder == null || chunkHolder.getTicketLevel() > minLevel) { -@@ -407,10 +476,10 @@ public final class ChunkTaskScheduler { - } - } - } finally { -- this.schedulingLock.unlock(); -+ this.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - } finally { -- this.chunkHolderManager.ticketLock.unlock(); -+ this.chunkHolderManager.ticketLockArea.unlock(ticketLock); // Folia - use area based lock to reduce contention - } - - for (int i = 0, len = tasks.size(); i < len; ++i) { -@@ -429,10 +498,11 @@ public final class ChunkTaskScheduler { - }, priority); - return; - } -- if (this.chunkHolderManager.ticketLock.isHeldByCurrentThread()) { -+ final int accessRadius = getAccessRadius(toStatus); // Folia - use area based lock to reduce contention -+ if (this.chunkHolderManager.ticketLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) { // Folia - use area based lock to reduce contention - throw new IllegalStateException("Cannot schedule chunk load during ticket level update"); - } -- if (this.schedulingLock.isHeldByCurrentThread()) { -+ if (this.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ, accessRadius)) { // Folia - use area based lock to reduce contention - throw new IllegalStateException("Cannot schedule chunk loading recursively"); - } - -@@ -469,9 +539,9 @@ public final class ChunkTaskScheduler { - - final boolean scheduled; - final ChunkAccess chunk; -- this.chunkHolderManager.ticketLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ, accessRadius); // Folia - use area based lock to reduce contention - try { -- this.schedulingLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.schedulingLockArea.lock(chunkX, chunkZ, accessRadius); // Folia - use area based lock to reduce contention - try { - final NewChunkHolder chunkHolder = this.chunkHolderManager.getChunkHolder(chunkKey); - if (chunkHolder == null || chunkHolder.getTicketLevel() > minLevel) { -@@ -496,10 +566,10 @@ public final class ChunkTaskScheduler { - } - } - } finally { -- this.schedulingLock.unlock(); -+ this.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - } finally { -- this.chunkHolderManager.ticketLock.unlock(); -+ this.chunkHolderManager.ticketLockArea.unlock(ticketLock); // Folia - use area based lock to reduce contention - } - - for (int i = 0, len = tasks.size(); i < len; ++i) { -@@ -546,7 +616,7 @@ public final class ChunkTaskScheduler { - private ChunkProgressionTask schedule(final int chunkX, final int chunkZ, final ChunkStatus targetStatus, - final NewChunkHolder chunkHolder, final List allTasks, - final PrioritisedExecutor.Priority minPriority) { -- if (!this.schedulingLock.isHeldByCurrentThread()) { -+ if (!this.schedulingLockArea.isHeldByCurrentThread(chunkX, chunkZ, getAccessRadius(targetStatus))) { // Folia - use area based lock to reduce contention - throw new IllegalStateException("Not holding scheduling lock"); - } - -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java -index 12feb739a784a0108256451a37d94d041b7a5cdc..1ff6b138ccf4a1cefa719cd0b2b3af02d18a26fb 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java -@@ -74,7 +74,7 @@ public final class NewChunkHolder { - TickThread.ensureTickThread(this.world, this.chunkX, this.chunkZ, "Cannot sync load entity data off-main"); - final CompoundTag entityChunk; - final ChunkEntitySlices ret; -- this.scheduler.schedulingLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); // Folia - use area based lock to reduce contention - try { - if (this.entityChunk != null && (transientChunk || !this.entityChunk.isTransient())) { - return this.entityChunk; -@@ -106,7 +106,7 @@ public final class NewChunkHolder { - entityChunk = null; - } - } finally { -- this.scheduler.schedulingLock.unlock(); -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - - if (!transientChunk) { -@@ -145,7 +145,7 @@ public final class NewChunkHolder { - final List completeWaiters; - ChunkLoadTask.EntityDataLoadTask entityDataLoadTask = null; - boolean scheduleEntityTask = false; -- this.scheduler.schedulingLock.lock(); -+ ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); // Folia - use area based lock to reduce contention - try { - final List waiters = this.entityDataLoadTaskWaiters; - this.entityDataLoadTask = null; -@@ -177,7 +177,7 @@ public final class NewChunkHolder { - } - } - } finally { -- this.scheduler.schedulingLock.unlock(); -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - - if (scheduleEntityTask) { -@@ -191,11 +191,11 @@ public final class NewChunkHolder { - } - } - -- this.scheduler.schedulingLock.lock(); -+ schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); // Folia - use area based lock to reduce contention - try { - this.checkUnload(); - } finally { -- this.scheduler.schedulingLock.unlock(); -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - } - -@@ -206,7 +206,7 @@ public final class NewChunkHolder { - throw new IllegalStateException("Cannot load entity data, it is already loaded"); - } - // why not just acquire the lock? because the caller NEEDS to call isEntityChunkNBTLoaded before this! -- if (!this.scheduler.schedulingLock.isHeldByCurrentThread()) { -+ if (!this.scheduler.schedulingLockArea.isHeldByCurrentThread(this.chunkX, this.chunkZ)) { // Folia - use area based lock to reduce contention - throw new IllegalStateException("Must hold scheduling lock"); - } - -@@ -262,7 +262,7 @@ public final class NewChunkHolder { - final List completeWaiters; - ChunkLoadTask.PoiDataLoadTask poiDataLoadTask = null; - boolean schedulePoiTask = false; -- this.scheduler.schedulingLock.lock(); -+ ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); // Folia - use area based lock to reduce contention - try { - final List waiters = this.poiDataLoadTaskWaiters; - this.poiDataLoadTask = null; -@@ -294,7 +294,7 @@ public final class NewChunkHolder { - } - } - } finally { -- this.scheduler.schedulingLock.unlock(); -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - - if (schedulePoiTask) { -@@ -307,11 +307,11 @@ public final class NewChunkHolder { - callback.accept(result); - } - } -- this.scheduler.schedulingLock.lock(); -+ schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); // Folia - use area based lock to reduce contention - try { - this.checkUnload(); - } finally { -- this.scheduler.schedulingLock.unlock(); -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - } - -@@ -322,7 +322,7 @@ public final class NewChunkHolder { - throw new IllegalStateException("Cannot load poi data, it is already loaded"); - } - // why not just acquire the lock? because the caller NEEDS to call isPoiChunkLoaded before this! -- if (!this.scheduler.schedulingLock.isHeldByCurrentThread()) { -+ if (!this.scheduler.schedulingLockArea.isHeldByCurrentThread(this.chunkX, this.chunkZ)) { // Folia - use area based lock to reduce contention - throw new IllegalStateException("Must hold scheduling lock"); - } - -@@ -411,7 +411,8 @@ public final class NewChunkHolder { - - @Override - public boolean cancel() { -- this.chunkHolder.scheduler.schedulingLock.lock(); -+ final NewChunkHolder holder = this.chunkHolder; // Folia - use area based lock to reduce contention -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = holder.scheduler.schedulingLockArea.lock(holder.chunkX, holder.chunkZ); // Folia - use area based lock to reduce contention - try { - if (!this.completed) { - this.completed = true; -@@ -420,7 +421,7 @@ public final class NewChunkHolder { - } - return false; - } finally { -- this.chunkHolder.scheduler.schedulingLock.unlock(); -+ holder.scheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - } - } -@@ -714,10 +715,10 @@ public final class NewChunkHolder { - } - if (this.isSafeToUnload() == null) { - // ensure in unload queue -- this.scheduler.chunkHolderManager.unloadQueue.add(this); -+ this.scheduler.chunkHolderManager.unloadQueue.addChunk(this.chunkX, this.chunkZ); // Folia - use area based lock to reduce contention - } else { - // ensure not in unload queue -- this.scheduler.chunkHolderManager.unloadQueue.remove(this); -+ this.scheduler.chunkHolderManager.unloadQueue.removeChunk(this.chunkX, this.chunkZ); // Folia - use area based lock to reduce contention - } - } - -@@ -787,13 +788,13 @@ public final class NewChunkHolder { - RegionFileIOThread.scheduleSave(this.world, this.chunkX, this.chunkZ, data, RegionFileIOThread.RegionFileType.CHUNK_DATA); - } - this.chunkDataUnload.completable().complete(data); -- this.scheduler.schedulingLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); // Folia - use area based lock to reduce contention - try { - // can only write to these fields while holding the schedule lock - this.chunkDataUnload = null; - this.checkUnload(); - } finally { -- this.scheduler.schedulingLock.unlock(); -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - } - -@@ -830,12 +831,12 @@ public final class NewChunkHolder { - this.lastEntityUnload = null; - - if (entityChunk.unload()) { -- this.scheduler.schedulingLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); // Folia - use area based lock to reduce contention - try { - entityChunk.setTransient(true); - this.entityChunk = entityChunk; - } finally { -- this.scheduler.schedulingLock.unlock(); -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - } else { - this.world.getEntityLookup().entitySectionUnload(this.chunkX, this.chunkZ); -@@ -1206,7 +1207,7 @@ public final class NewChunkHolder { - // only call on main thread // Folia - update comment - private void onFullChunkLoadChange(final boolean loaded, final List changedFullStatus) { - // Folia start - chunk system fix - acquire scheduling lock -- this.scheduler.schedulingLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ, NEIGHBOUR_RADIUS); // Folia - use area based lock to reduce contention - try { - // Folia end - chunk system fix - acquire scheduling lock - for (int dz = -NEIGHBOUR_RADIUS; dz <= NEIGHBOUR_RADIUS; ++dz) { -@@ -1225,7 +1226,7 @@ public final class NewChunkHolder { - } - // Folia start - chunk system fix - acquire scheduling lock - } finally { -- this.scheduler.schedulingLock.unlock(); -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - // Folia end - chunk system fix - acquire scheduling lock - } -@@ -1265,7 +1266,7 @@ public final class NewChunkHolder { - // note: use opaque reads for chunk status read since we need it to be atomic - - // test if anything changed -- final long statusCheck = (long)CHUNK_STATUS_HANDLE.getOpaque((NewChunkHolder)this); -+ long statusCheck = (long)CHUNK_STATUS_HANDLE.getOpaque((NewChunkHolder)this); // Folia - use area based lock to reduce contention - if ((int)statusCheck == (int)(statusCheck >>> 32)) { - // nothing changed - return ret; -@@ -1274,14 +1275,23 @@ public final class NewChunkHolder { - final ChunkTaskScheduler scheduler = this.scheduler; - final ChunkHolderManager holderManager = scheduler.chunkHolderManager; - final int ticketKeep; -- final Long ticketId; -- holderManager.ticketLock.lock(); -+ // Folia start - use area based lock to reduce contention -+ final Long ticketId = Long.valueOf(holderManager.getNextStatusUpgradeId()); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = holderManager.ticketLockArea.lock(this.chunkX, this.chunkZ); -+ // Folia end - use area based lock to reduce contention - try { - ticketKeep = this.currentTicketLevel; -- ticketId = Long.valueOf(holderManager.getNextStatusUpgradeId()); -- holderManager.addTicketAtLevel(TicketType.STATUS_UPGRADE, this.chunkX, this.chunkZ, ticketKeep, ticketId); -+ // Folia start - use area based lock to reduce contention -+ statusCheck = (long)CHUNK_STATUS_HANDLE.getOpaque((NewChunkHolder)this); -+ // handle race condition where ticket level and target status is updated concurrently -+ if ((int)statusCheck == (int)(statusCheck >>> 32)) { -+ // nothing changed -+ return ret; -+ } -+ holderManager.addTicketAtLevel(TicketType.STATUS_UPGRADE, CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ), ticketKeep, ticketId, false); -+ // Folia end - use area based lock to reduce contention - } finally { -- holderManager.ticketLock.unlock(); -+ holderManager.ticketLockArea.unlock(ticketLock); // Folia - use area based lock to reduce contention - } - - this.processingFullStatus = true; -@@ -1292,11 +1302,11 @@ public final class NewChunkHolder { - ChunkHolder.FullChunkStatus nextState = getPendingChunkStatus(currStateEncoded); - if (currState == nextState) { - if (nextState == ChunkHolder.FullChunkStatus.INACCESSIBLE) { -- this.scheduler.schedulingLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); // Folia - use area based lock to reduce contention - try { - this.checkUnload(); - } finally { -- this.scheduler.schedulingLock.unlock(); -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - } - break; -@@ -1696,14 +1706,14 @@ public final class NewChunkHolder { - // this means we have to leave the ticket level update to handle the scheduling - } - final List changedLoadStatus = new ArrayList<>(); -- this.scheduler.schedulingLock.lock(); -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ, 2 * ChunkTaskScheduler.getMaxAccessRadius()); // Folia - use area based lock to reduce contention - theoretically, we could schedule a chunk at the max radius which performs another max radius access. So we need to double. - try { - for (int i = 0, len = neighbours.size(); i < len; ++i) { - neighbours.get(i).removeNeighbourUsingChunk(); - } - this.onChunkGenComplete(access, taskStatus, tasks, changedLoadStatus); - } finally { -- this.scheduler.schedulingLock.unlock(); -+ this.scheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention - } - this.scheduler.chunkHolderManager.addChangedStatuses(changedLoadStatus); - -diff --git a/src/main/java/io/papermc/paper/threadedregions/ChunkQueue.java b/src/main/java/io/papermc/paper/threadedregions/ChunkQueue.java -new file mode 100644 -index 0000000000000000000000000000000000000000..50417ac73d7695e8e4eb463a906034bbd31b384b ---- /dev/null -+++ b/src/main/java/io/papermc/paper/threadedregions/ChunkQueue.java -@@ -0,0 +1,191 @@ -+package io.papermc.paper.threadedregions; -+ -+import it.unimi.dsi.fastutil.HashCommon; -+import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.Map; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.concurrent.atomic.AtomicLong; -+ -+public final class ChunkQueue { -+ -+ public final int coordinateShift; -+ private final AtomicLong orderGenerator = new AtomicLong(); -+ private final ConcurrentHashMap unloadSections = new ConcurrentHashMap<>(); -+ -+ /* -+ * Note: write operations do not occur in parallel for any given section. -+ * Note: coordinateShift <= region shift in order for retrieveForCurrentRegion() to function correctly -+ */ -+ -+ public ChunkQueue(final int coordinateShift) { -+ this.coordinateShift = coordinateShift; -+ } -+ -+ public static record SectionToUnload(int sectionX, int sectionZ, Coordinate coord, long order, int count) {} -+ -+ public List retrieveForAllRegions() { -+ final List ret = new ArrayList<>(); -+ -+ for (final Map.Entry entry : this.unloadSections.entrySet()) { -+ final Coordinate coord = entry.getKey(); -+ final long key = coord.key; -+ final UnloadSection section = entry.getValue(); -+ final int sectionX = Coordinate.x(key); -+ final int sectionZ = Coordinate.z(key); -+ -+ ret.add(new SectionToUnload(sectionX, sectionZ, coord, section.order, section.chunks.size())); -+ } -+ -+ ret.sort((final SectionToUnload s1, final SectionToUnload s2) -> { -+ return Long.compare(s1.order, s2.order); -+ }); -+ -+ return ret; -+ } -+ -+ public List retrieveForCurrentRegion() { -+ final ThreadedRegionizer.ThreadedRegion region = -+ TickRegionScheduler.getCurrentRegion(); -+ final ThreadedRegionizer regionizer = region.regioniser; -+ final int shift = this.coordinateShift; -+ -+ final List ret = new ArrayList<>(); -+ -+ for (final Map.Entry entry : this.unloadSections.entrySet()) { -+ final Coordinate coord = entry.getKey(); -+ final long key = coord.key; -+ final UnloadSection section = entry.getValue(); -+ final int sectionX = Coordinate.x(key); -+ final int sectionZ = Coordinate.z(key); -+ final int chunkX = sectionX << shift; -+ final int chunkZ = sectionZ << shift; -+ -+ if (regionizer.getRegionAtUnsynchronised(chunkX, chunkZ) != region) { -+ continue; -+ } -+ -+ ret.add(new SectionToUnload(sectionX, sectionZ, coord, section.order, section.chunks.size())); -+ } -+ -+ ret.sort((final SectionToUnload s1, final SectionToUnload s2) -> { -+ return Long.compare(s1.order, s2.order); -+ }); -+ -+ return ret; -+ } -+ -+ public UnloadSection getSectionUnsynchronized(final int sectionX, final int sectionZ) { -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ return this.unloadSections.get(coordinate); -+ } -+ -+ public UnloadSection removeSection(final int sectionX, final int sectionZ) { -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ return this.unloadSections.remove(coordinate); -+ } -+ -+ // write operation -+ public boolean addChunk(final int chunkX, final int chunkZ) { -+ final int shift = this.coordinateShift; -+ final int sectionX = chunkX >> shift; -+ final int sectionZ = chunkZ >> shift; -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ final long chunkKey = Coordinate.key(chunkX, chunkZ); -+ -+ UnloadSection section = this.unloadSections.get(coordinate); -+ if (section == null) { -+ section = new UnloadSection(this.orderGenerator.getAndIncrement()); -+ // write operations do not occur in parallel for a given section -+ this.unloadSections.put(coordinate, section); -+ } -+ -+ return section.chunks.add(chunkKey); -+ } -+ -+ // write operation -+ public boolean removeChunk(final int chunkX, final int chunkZ) { -+ final int shift = this.coordinateShift; -+ final int sectionX = chunkX >> shift; -+ final int sectionZ = chunkZ >> shift; -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ final long chunkKey = Coordinate.key(chunkX, chunkZ); -+ -+ final UnloadSection section = this.unloadSections.get(coordinate); -+ -+ if (section == null) { -+ return false; -+ } -+ -+ if (!section.chunks.remove(chunkKey)) { -+ return false; -+ } -+ -+ if (section.chunks.isEmpty()) { -+ this.unloadSections.remove(coordinate); -+ } -+ -+ return true; -+ } -+ -+ public static final class UnloadSection { -+ -+ public final long order; -+ public final LongLinkedOpenHashSet chunks = new LongLinkedOpenHashSet(); -+ -+ public UnloadSection(final long order) { -+ this.order = order; -+ } -+ } -+ -+ private static final class Coordinate implements Comparable { -+ -+ public final long key; -+ -+ public Coordinate(final long key) { -+ this.key = key; -+ } -+ -+ public Coordinate(final int x, final int z) { -+ this.key = key(x, z); -+ } -+ -+ public static long key(final int x, final int z) { -+ return ((long)z << 32) | (x & 0xFFFFFFFFL); -+ } -+ -+ public static int x(final long key) { -+ return (int)key; -+ } -+ -+ public static int z(final long key) { -+ return (int)(key >>> 32); -+ } -+ -+ @Override -+ public int hashCode() { -+ return (int)HashCommon.mix(this.key); -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ -+ if (!(obj instanceof Coordinate other)) { -+ return false; -+ } -+ -+ return this.key == other.key; -+ } -+ -+ // This class is intended for HashMap/ConcurrentHashMap usage, which do treeify bin nodes if the chain -+ // is too large. So we should implement compareTo to help. -+ @Override -+ public int compareTo(final Coordinate other) { -+ return Long.compare(this.key, other.key); -+ } -+ } -+} -diff --git a/src/main/java/io/papermc/paper/threadedregions/RegionizedServer.java b/src/main/java/io/papermc/paper/threadedregions/RegionizedServer.java -index 6c1d55144f044f39926ddf998104950b9efe3ee1..8e31c6ee9ee16aff699e124a9b0554eaafa5c1ac 100644 ---- a/src/main/java/io/papermc/paper/threadedregions/RegionizedServer.java -+++ b/src/main/java/io/papermc/paper/threadedregions/RegionizedServer.java -@@ -185,7 +185,96 @@ public final class RegionizedServer { - private long lastServerStatus; - private long tickCount; - -+ /* -+ private final java.util.Random random = new java.util.Random(4L); -+ private final List> walkers = -+ new java.util.ArrayList<>(); -+ static final int PLAYERS = 100; -+ static final int RAD_BLOCKS = 10000; -+ static final int RAD = RAD_BLOCKS >> 4; -+ static final int RAD_BIG_BLOCKS = 100_000; -+ static final int RAD_BIG = RAD_BIG_BLOCKS >> 4; -+ static final int VD = 4; -+ static final int BIG_PLAYERS = 50; -+ static final double WALK_CHANCE = 0.10; -+ static final double TP_CHANCE = 0.01; -+ -+ private ServerLevel getWorld() { -+ return this.worlds.get(0); -+ } -+ -+ private void init2() { -+ for (int i = 0; i < PLAYERS; ++i) { -+ int rad = i < BIG_PLAYERS ? RAD_BIG : RAD; -+ int posX = this.random.nextInt(-rad, rad + 1); -+ int posZ = this.random.nextInt(-rad, rad + 1); -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap<>(null) { -+ @Override -+ protected void addCallback(Void parameter, int chunkX, int chunkZ) { -+ ServerLevel world = RegionizedServer.this.getWorld(); -+ world.chunkTaskScheduler.chunkHolderManager.addTicketAtLevel( -+ net.minecraft.server.level.TicketType.PLAYER, chunkX, chunkZ, io.papermc.paper.chunk.system.scheduling.ChunkHolderManager.ENTITY_TICKING_TICKET_LEVEL, new net.minecraft.world.level.ChunkPos(posX, posZ) -+ ); -+ } -+ -+ @Override -+ protected void removeCallback(Void parameter, int chunkX, int chunkZ) { -+ ServerLevel world = RegionizedServer.this.getWorld(); -+ world.chunkTaskScheduler.chunkHolderManager.removeTicketAtLevel( -+ net.minecraft.server.level.TicketType.PLAYER, chunkX, chunkZ, io.papermc.paper.chunk.system.scheduling.ChunkHolderManager.ENTITY_TICKING_TICKET_LEVEL, new net.minecraft.world.level.ChunkPos(posX, posZ) -+ ); -+ } -+ }; -+ -+ map.add(posX, posZ, VD); -+ -+ walkers.add(map); -+ } -+ } -+ -+ private void randomWalk() { -+ if (this.walkers.isEmpty()) { -+ this.init2(); -+ return; -+ } -+ -+ for (int i = 0; i < PLAYERS; ++i) { -+ if (this.random.nextDouble() > WALK_CHANCE) { -+ continue; -+ } -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = this.walkers.get(i); -+ -+ int updateX = this.random.nextInt(-1, 2); -+ int updateZ = this.random.nextInt(-1, 2); -+ -+ map.update(map.lastChunkX + updateX, map.lastChunkZ + updateZ, VD); -+ } -+ -+ for (int i = 0; i < PLAYERS; ++i) { -+ if (random.nextDouble() >= TP_CHANCE) { -+ continue; -+ } -+ -+ int rad = i < BIG_PLAYERS ? RAD_BIG : RAD; -+ int posX = random.nextInt(-rad, rad + 1); -+ int posZ = random.nextInt(-rad, rad + 1); -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = walkers.get(i); -+ -+ map.update(posX, posZ, VD); -+ } -+ } -+ */ -+ - private void globalTick(final int tickCount) { -+ /* -+ if (false) { -+ io.papermc.paper.threadedregions.ThreadedTicketLevelPropagator.main(null); -+ } -+ this.randomWalk(); -+ */ - ++this.tickCount; - // expire invalid click command callbacks - io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue((int)this.tickCount); -@@ -311,6 +400,8 @@ public final class RegionizedServer { - this.tickTime(world, tickCount); - - world.updateTickData(); -+ -+ world.chunkTaskScheduler.chunkHolderManager.processTicketUpdates(); // Folia - use area based lock to reduce contention - required now to eventually process ticket updates - } - - private void updateRaids(final ServerLevel world) { -diff --git a/src/main/java/io/papermc/paper/threadedregions/RegionizedTaskQueue.java b/src/main/java/io/papermc/paper/threadedregions/RegionizedTaskQueue.java -index 4a095e69584d7dbbefafe6e0a4a1a1090172ac9e..2e4514e5a45db6e625ef7799b63a9285a3bc1030 100644 ---- a/src/main/java/io/papermc/paper/threadedregions/RegionizedTaskQueue.java -+++ b/src/main/java/io/papermc/paper/threadedregions/RegionizedTaskQueue.java -@@ -69,7 +69,7 @@ public final class RegionizedTaskQueue { - public static final class WorldRegionTaskData { - private final ServerLevel world; - private final MultiThreadedQueue globalChunkTask = new MultiThreadedQueue<>(); -- private final SWMRLong2ObjectHashTable referenceCounters = new SWMRLong2ObjectHashTable<>(); -+ private final java.util.concurrent.ConcurrentHashMap referenceCounters = new java.util.concurrent.ConcurrentHashMap<>(); // Folia - use area based lock to reduce contention - - public WorldRegionTaskData(final ServerLevel world) { - this.world = world; -@@ -115,17 +115,25 @@ public final class RegionizedTaskQueue { - ); - } - -+ // Folia start - use area based lock to reduce contention -+ private void processTicketUpdates(final long coord) { -+ this.world.chunkTaskScheduler.chunkHolderManager.processTicketUpdates(CoordinateUtils.getChunkX(coord), CoordinateUtils.getChunkZ(coord)); -+ } -+ // Folia end - use area based lock to reduce contention -+ - private void decrementReference(final AtomicLong reference, final long coord) { - final long val = reference.decrementAndGet(); - if (val == 0L) { -- final ReentrantLock ticketLock = this.world.chunkTaskScheduler.chunkHolderManager.ticketLock; -- ticketLock.lock(); -+ final int chunkX = CoordinateUtils.getChunkX(coord); // Folia - use area based lock to reduce contention -+ final int chunkZ = CoordinateUtils.getChunkZ(coord); // Folia - use area based lock to reduce contention -+ final io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkCoordinate key = new io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkCoordinate(coord); // Folia - use area based lock to reduce contention -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.world.chunkTaskScheduler.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ); // Folia - use area based lock to reduce contention - try { -- if (this.referenceCounters.remove(coord, reference)) { -+ if (this.referenceCounters.remove(key, reference)) { // Folia - use area based lock to reduce contention - WorldRegionTaskData.this.removeTicket(coord); - } // else: race condition, something replaced our reference - not our issue anymore - } finally { -- ticketLock.unlock(); -+ this.world.chunkTaskScheduler.chunkHolderManager.ticketLockArea.unlock(ticketLock); // Folia - use area based lock to reduce contention - } - } else if (val < 0L) { - throw new IllegalStateException("Reference count < 0: " + val); -@@ -133,7 +141,8 @@ public final class RegionizedTaskQueue { - } - - private AtomicLong incrementReference(final long coord) { -- final AtomicLong ret = this.referenceCounters.get(coord); -+ final io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkCoordinate key = new io.papermc.paper.chunk.system.io.RegionFileIOThread.ChunkCoordinate(coord); // Folia - use area based lock to reduce contention -+ final AtomicLong ret = this.referenceCounters.get(key); // Folia - use area based lock to reduce contention - if (ret != null) { - // try to fast acquire counter - int failures = 0; -@@ -156,41 +165,54 @@ public final class RegionizedTaskQueue { - } - - // slow acquire -- final ReentrantLock ticketLock = this.world.chunkTaskScheduler.chunkHolderManager.ticketLock; -- ticketLock.lock(); -+ final int chunkX = CoordinateUtils.getChunkX(coord); // Folia - use area based lock to reduce contention -+ final int chunkZ = CoordinateUtils.getChunkZ(coord); // Folia - use area based lock to reduce contention -+ final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node ticketLock = this.world.chunkTaskScheduler.chunkHolderManager.ticketLockArea.lock(chunkX, chunkZ); // Folia - use area based lock to reduce contention -+ final AtomicLong ret2; -+ final boolean processTicketUpdates; - try { - final AtomicLong replace = new AtomicLong(1L); -- final AtomicLong valueInMap = this.referenceCounters.putIfAbsent(coord, replace); -+ final AtomicLong valueInMap = this.referenceCounters.putIfAbsent(key, replace); // Folia - use area based lock to reduce contention - if (valueInMap == null) { - // replaced, we should usually be here - this.addTicket(coord); -- return replace; -- } // else: need to attempt to acquire the reference -+ ret2 = replace; -+ processTicketUpdates = true; -+ } else { -+ processTicketUpdates = false; -+ int failures = 0; -+ for (long curr = valueInMap.get();;) { -+ if (curr == 0L) { -+ // don't need to add ticket here, since ticket is only removed during the lock -+ // we just need to replace the value in the map so that the thread removing fails and doesn't -+ // remove the ticket (see decrementReference) -+ this.referenceCounters.put(key, replace); // Folia - use area based lock to reduce contention -+ ret2 = replace; -+ break; -+ } - -- int failures = 0; -- for (long curr = valueInMap.get();;) { -- if (curr == 0L) { -- // don't need to add ticket here, since ticket is only removed during the lock -- // we just need to replace the value in the map so that the thread removing fails and doesn't -- // remove the ticket (see decrementReference) -- this.referenceCounters.put(coord, replace); -- return replace; -- } -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } - -- for (int i = 0; i < failures; ++i) { -- ConcurrentUtil.backoff(); -- } -+ if (curr == (curr = valueInMap.compareAndExchange(curr, curr + 1L))) { -+ // acquired -+ ret2 = valueInMap; -+ break; -+ } - -- if (curr == (curr = valueInMap.compareAndExchange(curr, curr + 1L))) { -- // acquired -- return valueInMap; -+ ++failures; - } -- -- ++failures; - } - } finally { -- ticketLock.unlock(); -+ this.world.chunkTaskScheduler.chunkHolderManager.ticketLockArea.unlock(ticketLock); // Folia - use area based lock to reduce contention -+ } -+ -+ if (processTicketUpdates) { -+ this.processTicketUpdates(coord); - } -+ -+ return ret2; - } - } - -diff --git a/src/main/java/io/papermc/paper/threadedregions/ThreadedRegionizer.java b/src/main/java/io/papermc/paper/threadedregions/ThreadedRegionizer.java -index 5170b43743ea27a5c2aaee37d76f4e7e730fd808..1a4d820535f7b04671525c4f0e8691c9e82e075f 100644 ---- a/src/main/java/io/papermc/paper/threadedregions/ThreadedRegionizer.java -+++ b/src/main/java/io/papermc/paper/threadedregions/ThreadedRegionizer.java -@@ -674,6 +674,14 @@ public final class ThreadedRegionizer sections = new ConcurrentHashMap<>(); -+ -+ public ThreadedTicketLevelPropagator() { -+ this.updateQueue = new UpdateQueue(); -+ } -+ -+ // must hold ticket lock for: -+ // (posX & ~(SECTION_SIZE - 1), posZ & ~(SECTION_SIZE - 1)) to (posX | (SECTION_SIZE - 1), posZ | (SECTION_SIZE - 1)) -+ public void setSource(final int posX, final int posZ, final int to) { -+ if (to < 1 || to > MAX_SOURCE_LEVEL) { -+ throw new IllegalArgumentException("Source: " + to); -+ } -+ -+ final int sectionX = posX >> SECTION_SHIFT; -+ final int sectionZ = posZ >> SECTION_SHIFT; -+ -+ final Coordinate coordinate = new Coordinate(sectionX, sectionZ); -+ Section section = this.sections.get(coordinate); -+ if (section == null) { -+ if (null != this.sections.putIfAbsent(coordinate, section = new Section(sectionX, sectionZ))) { -+ throw new IllegalStateException("Race condition while creating new section"); -+ } -+ } -+ -+ final int localIdx = (posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); -+ final short sLocalIdx = (short)localIdx; -+ -+ final short sourceAndLevel = section.levels[localIdx]; -+ final int currentSource = (sourceAndLevel >>> 8) & 0xFF; -+ -+ if (currentSource == to) { -+ // nothing to do -+ // make sure to kill the current update, if any -+ section.queuedSources.replace(sLocalIdx, (byte)to); -+ return; -+ } -+ -+ if (section.queuedSources.put(sLocalIdx, (byte)to) == Section.NO_QUEUED_UPDATE && section.queuedSources.size() == 1) { -+ this.queueSectionUpdate(section); -+ } -+ } -+ -+ // must hold ticket lock for: -+ // (posX & ~(SECTION_SIZE - 1), posZ & ~(SECTION_SIZE - 1)) to (posX | (SECTION_SIZE - 1), posZ | (SECTION_SIZE - 1)) -+ public void removeSource(final int posX, final int posZ) { -+ final int sectionX = posX >> SECTION_SHIFT; -+ final int sectionZ = posZ >> SECTION_SHIFT; -+ -+ final Coordinate coordinate = new Coordinate(sectionX, sectionZ); -+ final Section section = this.sections.get(coordinate); -+ -+ if (section == null) { -+ return; -+ } -+ -+ final int localIdx = (posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); -+ final short sLocalIdx = (short)localIdx; -+ -+ final int currentSource = (section.levels[localIdx] >>> 8) & 0xFF; -+ -+ if (currentSource == 0) { -+ // we use replace here so that we do not possibly multi-queue a section for an update -+ section.queuedSources.replace(sLocalIdx, (byte)0); -+ return; -+ } -+ -+ if (section.queuedSources.put(sLocalIdx, (byte)0) == Section.NO_QUEUED_UPDATE && section.queuedSources.size() == 1) { -+ this.queueSectionUpdate(section); -+ } -+ } -+ -+ private void queueSectionUpdate(final Section section) { -+ this.updateQueue.append(new UpdateQueue.UpdateQueueNode(section, null)); -+ } -+ -+ public boolean hasPendingUpdates() { -+ return !this.updateQueue.isEmpty(); -+ } -+ -+ // holds ticket lock for every chunk section represented by any position in the key set -+ // updates is modifiable and passed to processSchedulingUpdates after this call -+ protected abstract void processLevelUpdates(final Long2ByteLinkedOpenHashMap updates); -+ -+ // holds ticket lock for every chunk section represented by any position in the key set -+ // holds scheduling lock in max access radius for every position held by the ticket lock -+ // updates is cleared after this call -+ protected abstract void processSchedulingUpdates(final Long2ByteLinkedOpenHashMap updates, final List scheduledTasks, -+ final List changedFullStatus); -+ -+ // must hold ticket lock for every position in the sections in one radius around sectionX,sectionZ -+ public boolean performUpdate(final int sectionX, final int sectionZ, final ReentrantAreaLock schedulingLock, -+ final List scheduledTasks, final List changedFullStatus) { -+ if (!this.hasPendingUpdates()) { -+ return false; -+ } -+ -+ final Coordinate coordinate = new Coordinate(Coordinate.key(sectionX, sectionZ)); -+ final Section section = this.sections.get(coordinate); -+ -+ if (section == null || section.queuedSources.isEmpty()) { -+ // no section or no updates -+ return false; -+ } -+ -+ final Propagator propagator = Propagator.acquirePropagator(); -+ final boolean ret = this.performUpdate(section, null, propagator, -+ null, schedulingLock, scheduledTasks, changedFullStatus -+ ); -+ Propagator.returnPropagator(propagator); -+ return ret; -+ } -+ -+ private boolean performUpdate(final Section section, final UpdateQueue.UpdateQueueNode node, final Propagator propagator, -+ final ReentrantAreaLock ticketLock, final ReentrantAreaLock schedulingLock, -+ final List scheduledTasks, final List changedFullStatus) { -+ final int sectionX = section.sectionX; -+ final int sectionZ = section.sectionZ; -+ -+ final int rad1MinX = (sectionX - 1) << SECTION_SHIFT; -+ final int rad1MinZ = (sectionZ - 1) << SECTION_SHIFT; -+ final int rad1MaxX = ((sectionX + 1) << SECTION_SHIFT) | (SECTION_SIZE - 1); -+ final int rad1MaxZ = ((sectionZ + 1) << SECTION_SHIFT) | (SECTION_SIZE - 1); -+ -+ // set up encode offset first as we need to queue level changes _before_ -+ propagator.setupEncodeOffset(sectionX, sectionZ); -+ -+ final int coordinateOffset = propagator.coordinateOffset; -+ -+ final ReentrantAreaLock.Node ticketNode = ticketLock == null ? null : ticketLock.lock(rad1MinX, rad1MinZ, rad1MaxX, rad1MaxZ); -+ final boolean ret; -+ try { -+ // first, check if this update was stolen -+ if (section != this.sections.get(new Coordinate(sectionX, sectionZ))) { -+ // occurs when a stolen update deletes this section -+ // it is possible that another update is scheduled, but that one will have the correct section -+ if (node != null) { -+ this.updateQueue.remove(node); -+ } -+ return false; -+ } -+ -+ final int oldSourceSize = section.sources.size(); -+ -+ // process pending sources -+ for (final Iterator iterator = section.queuedSources.short2ByteEntrySet().fastIterator(); iterator.hasNext();) { -+ final Short2ByteMap.Entry entry = iterator.next(); -+ final int pos = (int)entry.getShortKey(); -+ final int posX = (pos & (SECTION_SIZE - 1)) | (sectionX << SECTION_SHIFT); -+ final int posZ = ((pos >> SECTION_SHIFT) & (SECTION_SIZE - 1)) | (sectionZ << SECTION_SHIFT); -+ final int newSource = (int)entry.getByteValue(); -+ -+ final short currentEncoded = section.levels[pos]; -+ final int currLevel = currentEncoded & 0xFF; -+ final int prevSource = (currentEncoded >>> 8) & 0xFF; -+ -+ if (prevSource == newSource) { -+ // nothing changed -+ continue; -+ } -+ -+ if ((prevSource < currLevel && newSource <= currLevel) || newSource == currLevel) { -+ // just update the source, don't need to propagate change -+ section.levels[pos] = (short)(currLevel | (newSource << 8)); -+ // level is unchanged, don't add to changed positions -+ } else { -+ // set current level and current source to new source -+ section.levels[pos] = (short)(newSource | (newSource << 8)); -+ // must add to updated positions in case this is final -+ propagator.updatedPositions.put(Coordinate.key(posX, posZ), (byte)newSource); -+ if (newSource != 0) { -+ // queue increase with new source level -+ propagator.appendToIncreaseQueue( -+ ((long)(posX + (posZ << Propagator.COORDINATE_BITS) + coordinateOffset) & ((1L << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS)) - 1)) | -+ ((newSource & (LEVEL_COUNT - 1L)) << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS)) | -+ (Propagator.ALL_DIRECTIONS_BITSET << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS + LEVEL_BITS)) -+ ); -+ } -+ // queue decrease with previous level -+ if (newSource < currLevel) { -+ propagator.appendToDecreaseQueue( -+ ((long)(posX + (posZ << Propagator.COORDINATE_BITS) + coordinateOffset) & ((1L << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS)) - 1)) | -+ ((currLevel & (LEVEL_COUNT - 1L)) << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS)) | -+ (Propagator.ALL_DIRECTIONS_BITSET << (Propagator.COORDINATE_BITS + Propagator.COORDINATE_BITS + LEVEL_BITS)) -+ ); -+ } -+ } -+ -+ if (newSource == 0) { -+ // prevSource != newSource, so we are removing this source -+ section.sources.remove((short)pos); -+ } else if (prevSource == 0) { -+ // prevSource != newSource, so we are adding this source -+ section.sources.add((short)pos); -+ } -+ } -+ -+ section.queuedSources.clear(); -+ -+ final int newSourceSize = section.sources.size(); -+ -+ if (oldSourceSize == 0 && newSourceSize != 0) { -+ // need to make sure the sections in 1 radius are initialised -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ if ((dx | dz) == 0) { -+ continue; -+ } -+ final int offX = dx + sectionX; -+ final int offZ = dz + sectionZ; -+ final Coordinate coordinate = new Coordinate(offX, offZ); -+ final Section neighbour = this.sections.computeIfAbsent(coordinate, (final Coordinate keyInMap) -> { -+ return new Section(Coordinate.x(keyInMap.key), Coordinate.z(keyInMap.key)); -+ }); -+ -+ // increase ref count -+ ++neighbour.oneRadNeighboursWithSources; -+ if (neighbour.oneRadNeighboursWithSources <= 0 || neighbour.oneRadNeighboursWithSources > 8) { -+ throw new IllegalStateException(Integer.toString(neighbour.oneRadNeighboursWithSources)); -+ } -+ } -+ } -+ } -+ -+ if (propagator.hasUpdates()) { -+ propagator.setupCaches(this, sectionX, sectionZ, 1); -+ propagator.performDecrease(); -+ // don't need try-finally, as any exception will cause the propagator to not be returned -+ propagator.destroyCaches(); -+ } -+ -+ if (newSourceSize == 0) { -+ final boolean decrementRef = oldSourceSize != 0; -+ // check for section de-init -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ final int offX = dx + sectionX; -+ final int offZ = dz + sectionZ; -+ final Coordinate coordinate = new Coordinate(offX, offZ); -+ final Section neighbour = this.sections.get(coordinate); -+ -+ if (neighbour == null) { -+ if (oldSourceSize == 0 && (dx | dz) != 0) { -+ // since we don't have sources, this section is allowed to null -+ continue; -+ } -+ throw new IllegalStateException("??"); -+ } -+ -+ if (decrementRef && (dx | dz) != 0) { -+ // decrease ref count, but only for neighbours -+ --neighbour.oneRadNeighboursWithSources; -+ } -+ -+ // we need to check the current section for de-init as well -+ if (neighbour.oneRadNeighboursWithSources == 0) { -+ if (neighbour.queuedSources.isEmpty() && neighbour.sources.isEmpty()) { -+ // need to de-init -+ this.sections.remove(coordinate); -+ } // else: neighbour is queued for an update, and it will de-init itself -+ } else if (neighbour.oneRadNeighboursWithSources < 0 || neighbour.oneRadNeighboursWithSources > 8) { -+ throw new IllegalStateException(Integer.toString(neighbour.oneRadNeighboursWithSources)); -+ } -+ } -+ } -+ } -+ -+ -+ ret = !propagator.updatedPositions.isEmpty(); -+ -+ if (ret) { -+ this.processLevelUpdates(propagator.updatedPositions); -+ -+ if (!propagator.updatedPositions.isEmpty()) { -+ // now we can actually update the ticket levels in the chunk holders -+ final int maxScheduleRadius = 2 * ChunkTaskScheduler.getMaxAccessRadius(); -+ -+ // allow the chunkholders to process ticket level updates without needing to acquire the schedule lock every time -+ final ReentrantAreaLock.Node schedulingNode = schedulingLock.lock( -+ rad1MinX - maxScheduleRadius, rad1MinZ - maxScheduleRadius, -+ rad1MaxX + maxScheduleRadius, rad1MaxZ + maxScheduleRadius -+ ); -+ try { -+ this.processSchedulingUpdates(propagator.updatedPositions, scheduledTasks, changedFullStatus); -+ } finally { -+ schedulingLock.unlock(schedulingNode); -+ } -+ } -+ -+ propagator.updatedPositions.clear(); -+ } -+ } finally { -+ if (ticketLock != null) { -+ ticketLock.unlock(ticketNode); -+ } -+ } -+ -+ // finished -+ if (node != null) { -+ this.updateQueue.remove(node); -+ } -+ -+ return ret; -+ } -+ -+ public boolean performUpdates(final ReentrantAreaLock ticketLock, final ReentrantAreaLock schedulingLock, -+ final List scheduledTasks, final List changedFullStatus) { -+ if (this.updateQueue.isEmpty()) { -+ return false; -+ } -+ -+ final long maxOrder = this.updateQueue.getLastOrder(); -+ -+ boolean updated = false; -+ Propagator propagator = null; -+ -+ for (;;) { -+ final UpdateQueue.UpdateQueueNode toUpdate = this.updateQueue.acquireNextToUpdate(maxOrder); -+ if (toUpdate == null) { -+ this.updateQueue.awaitFirst(maxOrder); -+ -+ if (!this.updateQueue.hasRemainingUpdates(maxOrder)) { -+ if (propagator != null) { -+ Propagator.returnPropagator(propagator); -+ } -+ return updated; -+ } -+ -+ continue; -+ } -+ -+ if (propagator == null) { -+ propagator = Propagator.acquirePropagator(); -+ } -+ -+ updated |= this.performUpdate(toUpdate.section, toUpdate, propagator, ticketLock, schedulingLock, scheduledTasks, changedFullStatus); -+ } -+ } -+ -+ private static final class UpdateQueue { -+ -+ private volatile UpdateQueueNode head; -+ private volatile UpdateQueueNode tail; -+ private volatile UpdateQueueNode lastUpdating; -+ -+ protected static final VarHandle HEAD_HANDLE = ConcurrentUtil.getVarHandle(UpdateQueue.class, "head", UpdateQueueNode.class); -+ protected static final VarHandle TAIL_HANDLE = ConcurrentUtil.getVarHandle(UpdateQueue.class, "tail", UpdateQueueNode.class); -+ protected static final VarHandle LAST_UPDATING = ConcurrentUtil.getVarHandle(UpdateQueue.class, "lastUpdating", UpdateQueueNode.class); -+ -+ /* head */ -+ -+ protected final void setHeadPlain(final UpdateQueueNode newHead) { -+ HEAD_HANDLE.set(this, newHead); -+ } -+ -+ protected final void setHeadOpaque(final UpdateQueueNode newHead) { -+ HEAD_HANDLE.setOpaque(this, newHead); -+ } -+ -+ protected final UpdateQueueNode getHeadPlain() { -+ return (UpdateQueueNode)HEAD_HANDLE.get(this); -+ } -+ -+ protected final UpdateQueueNode getHeadOpaque() { -+ return (UpdateQueueNode)HEAD_HANDLE.getOpaque(this); -+ } -+ -+ protected final UpdateQueueNode getHeadAcquire() { -+ return (UpdateQueueNode)HEAD_HANDLE.getAcquire(this); -+ } -+ -+ /* tail */ -+ -+ protected final void setTailPlain(final UpdateQueueNode newTail) { -+ TAIL_HANDLE.set(this, newTail); -+ } -+ -+ protected final void setTailOpaque(final UpdateQueueNode newTail) { -+ TAIL_HANDLE.setOpaque(this, newTail); -+ } -+ -+ protected final UpdateQueueNode getTailPlain() { -+ return (UpdateQueueNode)TAIL_HANDLE.get(this); -+ } -+ -+ protected final UpdateQueueNode getTailOpaque() { -+ return (UpdateQueueNode)TAIL_HANDLE.getOpaque(this); -+ } -+ -+ /* lastUpdating */ -+ -+ protected final UpdateQueueNode getLastUpdatingVolatile() { -+ return (UpdateQueueNode)LAST_UPDATING.getVolatile(this); -+ } -+ -+ protected final UpdateQueueNode compareAndExchangeLastUpdatingVolatile(final UpdateQueueNode expect, final UpdateQueueNode update) { -+ return (UpdateQueueNode)LAST_UPDATING.compareAndExchange(this, expect, update); -+ } -+ -+ public UpdateQueue() { -+ final UpdateQueueNode dummy = new UpdateQueueNode(null, null); -+ dummy.order = -1L; -+ dummy.preventAdds(); -+ -+ this.setHeadPlain(dummy); -+ this.setTailPlain(dummy); -+ } -+ -+ public boolean isEmpty() { -+ return this.peek() == null; -+ } -+ -+ public boolean hasRemainingUpdates(final long maxUpdate) { -+ final UpdateQueueNode node = this.peek(); -+ return node != null && node.order <= maxUpdate; -+ } -+ -+ public long getLastOrder() { -+ for (UpdateQueueNode tail = this.getTailOpaque(), curr = tail;;) { -+ final UpdateQueueNode next = curr.getNextVolatile(); -+ if (next == null) { -+ // try to update stale tail -+ if (this.getTailOpaque() == tail && curr != tail) { -+ this.setTailOpaque(curr); -+ } -+ return curr.order; -+ } -+ curr = next; -+ } -+ } -+ -+ public UpdateQueueNode acquireNextToUpdate(final long maxOrder) { -+ int failures = 0; -+ for (UpdateQueueNode prev = this.getLastUpdatingVolatile();;) { -+ UpdateQueueNode next = prev == null ? this.peek() : prev.next; -+ -+ if (next == null || next.order > maxOrder) { -+ return null; -+ } -+ -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (prev == (prev = this.compareAndExchangeLastUpdatingVolatile(prev, next))) { -+ return next; -+ } -+ -+ ++failures; -+ } -+ } -+ -+ public void awaitFirst(final long maxOrder) { -+ final UpdateQueueNode earliest = this.peek(); -+ if (earliest == null || earliest.order > maxOrder) { -+ return; -+ } -+ -+ final Thread currThread = Thread.currentThread(); -+ // we do not use add-blocking because we use the nullability of the section to block -+ // remove() does not begin to poll from the wait queue until the section is null'd, -+ // and so provided we check the nullability before parking there is no ordering of these operations -+ // such that remove() finishes polling from the wait queue while section is not null -+ earliest.add(currThread); -+ -+ // wait until completed -+ while (earliest.getSectionVolatile() != null) { -+ LockSupport.park(); -+ } -+ } -+ -+ public UpdateQueueNode peek() { -+ for (UpdateQueueNode head = this.getHeadOpaque(), curr = head;;) { -+ final UpdateQueueNode next = curr.getNextVolatile(); -+ final Section element = curr.getSectionVolatile(); /* Likely in sync */ -+ -+ if (element != null) { -+ if (this.getHeadOpaque() == head && curr != head) { -+ this.setHeadOpaque(curr); -+ } -+ return curr; -+ } -+ -+ if (next == null) { -+ if (this.getHeadOpaque() == head && curr != head) { -+ this.setHeadOpaque(curr); -+ } -+ return null; -+ } -+ curr = next; -+ } -+ } -+ -+ public void remove(final UpdateQueueNode node) { -+ // mark as removed -+ node.setSectionVolatile(null); -+ -+ // use peek to advance head -+ this.peek(); -+ -+ // unpark any waiters / block the wait queue -+ Thread unpark; -+ while ((unpark = node.poll()) != null) { -+ LockSupport.unpark(unpark); -+ } -+ } -+ -+ public void append(final UpdateQueueNode node) { -+ int failures = 0; -+ -+ for (UpdateQueueNode currTail = this.getTailOpaque(), curr = currTail;;) { -+ /* It has been experimentally shown that placing the read before the backoff results in significantly greater performance */ -+ /* It is likely due to a cache miss caused by another write to the next field */ -+ final UpdateQueueNode next = curr.getNextVolatile(); -+ -+ for (int i = 0; i < failures; ++i) { -+ ConcurrentUtil.backoff(); -+ } -+ -+ if (next == null) { -+ node.order = curr.order + 1L; -+ final UpdateQueueNode compared = curr.compareExchangeNextVolatile(null, node); -+ -+ if (compared == null) { -+ /* Added */ -+ /* Avoid CASing on tail more than we need to */ -+ /* CAS to avoid setting an out-of-date tail */ -+ if (this.getTailOpaque() == currTail) { -+ this.setTailOpaque(node); -+ } -+ return; -+ } -+ -+ ++failures; -+ curr = compared; -+ continue; -+ } -+ -+ if (curr == currTail) { -+ /* Tail is likely not up-to-date */ -+ curr = next; -+ } else { -+ /* Try to update to tail */ -+ if (currTail == (currTail = this.getTailOpaque())) { -+ curr = next; -+ } else { -+ curr = currTail; -+ } -+ } -+ } -+ } -+ -+ // each node also represents a set of waiters, represented by the MTQ -+ // if the queue is add-blocked, then the update is complete -+ private static final class UpdateQueueNode extends MultiThreadedQueue { -+ private long order; -+ private Section section; -+ private volatile UpdateQueueNode next; -+ -+ protected static final VarHandle SECTION_HANDLE = ConcurrentUtil.getVarHandle(UpdateQueueNode.class, "section", Section.class); -+ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(UpdateQueueNode.class, "next", UpdateQueueNode.class); -+ -+ public UpdateQueueNode(final Section section, final UpdateQueueNode next) { -+ SECTION_HANDLE.set(this, section); -+ NEXT_HANDLE.set(this, next); -+ } -+ -+ /* section */ -+ -+ protected final Section getSectionPlain() { -+ return (Section)SECTION_HANDLE.get(this); -+ } -+ -+ protected final Section getSectionVolatile() { -+ return (Section)SECTION_HANDLE.getVolatile(this); -+ } -+ -+ protected final void setSectionPlain(final Section update) { -+ SECTION_HANDLE.set(this, update); -+ } -+ -+ protected final void setSectionOpaque(final Section update) { -+ SECTION_HANDLE.setOpaque(this, update); -+ } -+ -+ protected final void setSectionVolatile(final Section update) { -+ SECTION_HANDLE.setVolatile(this, update); -+ } -+ -+ protected final Section getAndSetSectionVolatile(final Section update) { -+ return (Section)SECTION_HANDLE.getAndSet(this, update); -+ } -+ -+ protected final Section compareExchangeSectionVolatile(final Section expect, final Section update) { -+ return (Section)SECTION_HANDLE.compareAndExchange(this, expect, update); -+ } -+ -+ /* next */ -+ -+ protected final UpdateQueueNode getNextPlain() { -+ return (UpdateQueueNode)NEXT_HANDLE.get(this); -+ } -+ -+ protected final UpdateQueueNode getNextOpaque() { -+ return (UpdateQueueNode)NEXT_HANDLE.getOpaque(this); -+ } -+ -+ protected final UpdateQueueNode getNextAcquire() { -+ return (UpdateQueueNode)NEXT_HANDLE.getAcquire(this); -+ } -+ -+ protected final UpdateQueueNode getNextVolatile() { -+ return (UpdateQueueNode)NEXT_HANDLE.getVolatile(this); -+ } -+ -+ protected final void setNextPlain(final UpdateQueueNode next) { -+ NEXT_HANDLE.set(this, next); -+ } -+ -+ protected final void setNextVolatile(final UpdateQueueNode next) { -+ NEXT_HANDLE.setVolatile(this, next); -+ } -+ -+ protected final UpdateQueueNode compareExchangeNextVolatile(final UpdateQueueNode expect, final UpdateQueueNode set) { -+ return (UpdateQueueNode)NEXT_HANDLE.compareAndExchange(this, expect, set); -+ } -+ } -+ } -+ -+ private static final class Section { -+ -+ // upper 8 bits: sources, lower 8 bits: level -+ // if we REALLY wanted to get crazy, we could make the increase propagator use MethodHandles#byteArrayViewVarHandle -+ // to read and write the lower 8 bits of this array directly rather than reading, updating the bits, then writing back. -+ private final short[] levels = new short[SECTION_SIZE * SECTION_SIZE]; -+ // set of local positions that represent sources -+ private final ShortOpenHashSet sources = new ShortOpenHashSet(); -+ // map of local index to new source level -+ // the source level _cannot_ be updated in the backing storage immediately since the update -+ private static final byte NO_QUEUED_UPDATE = (byte)-1; -+ private final Short2ByteLinkedOpenHashMap queuedSources = new Short2ByteLinkedOpenHashMap(); -+ { -+ this.queuedSources.defaultReturnValue(NO_QUEUED_UPDATE); -+ } -+ private int oneRadNeighboursWithSources = 0; -+ -+ public final int sectionX; -+ public final int sectionZ; -+ -+ public Section(final int sectionX, final int sectionZ) { -+ this.sectionX = sectionX; -+ this.sectionZ = sectionZ; -+ } -+ -+ public boolean isZero() { -+ for (final short val : this.levels) { -+ if (val != 0) { -+ return false; -+ } -+ } -+ return true; -+ } -+ -+ @Override -+ public String toString() { -+ final StringBuilder ret = new StringBuilder(); -+ -+ for (int x = 0; x < SECTION_SIZE; ++x) { -+ ret.append("levels x=").append(x).append("\n"); -+ for (int z = 0; z < SECTION_SIZE; ++z) { -+ final short v = this.levels[x | (z << SECTION_SHIFT)]; -+ ret.append(v & 0xFF).append("."); -+ } -+ ret.append("\n"); -+ ret.append("sources x=").append(x).append("\n"); -+ for (int z = 0; z < SECTION_SIZE; ++z) { -+ final short v = this.levels[x | (z << SECTION_SHIFT)]; -+ ret.append((v >>> 8) & 0xFF).append("."); -+ } -+ ret.append("\n\n"); -+ } -+ -+ return ret.toString(); -+ } -+ } -+ -+ -+ private static final class Propagator { -+ -+ private static final ArrayDeque CACHED_PROPAGATORS = new ArrayDeque<>(); -+ private static final int MAX_PROPAGATORS = Runtime.getRuntime().availableProcessors() * 2; -+ -+ private static Propagator acquirePropagator() { -+ synchronized (CACHED_PROPAGATORS) { -+ final Propagator ret = CACHED_PROPAGATORS.pollFirst(); -+ if (ret != null) { -+ return ret; -+ } -+ } -+ return new Propagator(); -+ } -+ -+ private static void returnPropagator(final Propagator propagator) { -+ synchronized (CACHED_PROPAGATORS) { -+ if (CACHED_PROPAGATORS.size() < MAX_PROPAGATORS) { -+ CACHED_PROPAGATORS.add(propagator); -+ } -+ } -+ } -+ -+ private static final int SECTION_RADIUS = 2; -+ private static final int SECTION_CACHE_WIDTH = 2 * SECTION_RADIUS + 1; -+ // minimum number of bits to represent [0, SECTION_SIZE * SECTION_CACHE_WIDTH) -+ private static final int COORDINATE_BITS = 9; -+ private static final int COORDINATE_SIZE = 1 << COORDINATE_BITS; -+ static { -+ if ((SECTION_SIZE * SECTION_CACHE_WIDTH) > (1 << COORDINATE_BITS)) { -+ throw new IllegalStateException("Adjust COORDINATE_BITS"); -+ } -+ } -+ // index = x + (z * SECTION_CACHE_WIDTH) -+ // (this requires x >= 0 and z >= 0) -+ private final Section[] sections = new Section[SECTION_CACHE_WIDTH * SECTION_CACHE_WIDTH]; -+ -+ private int encodeOffsetX; -+ private int encodeOffsetZ; -+ -+ private int coordinateOffset; -+ -+ private int encodeSectionOffsetX; -+ private int encodeSectionOffsetZ; -+ -+ private int sectionIndexOffset; -+ -+ public final boolean hasUpdates() { -+ return this.decreaseQueueInitialLength != 0 || this.increaseQueueInitialLength != 0; -+ } -+ -+ protected final void setupEncodeOffset(final int centerSectionX, final int centerSectionZ) { -+ final int maxCoordinate = (SECTION_RADIUS * SECTION_SIZE - 1); -+ // must have that encoded >= 0 -+ // coordinates can range from [-maxCoordinate + centerSection*SECTION_SIZE, maxCoordinate + centerSection*SECTION_SIZE] -+ // we want a range of [0, maxCoordinate*2] -+ // so, 0 = -maxCoordinate + centerSection*SECTION_SIZE + offset -+ this.encodeOffsetX = maxCoordinate - (centerSectionX << SECTION_SHIFT); -+ this.encodeOffsetZ = maxCoordinate - (centerSectionZ << SECTION_SHIFT); -+ -+ // encoded coordinates range from [0, SECTION_SIZE * SECTION_CACHE_WIDTH) -+ // coordinate index = (x + encodeOffsetX) + ((z + encodeOffsetZ) << COORDINATE_BITS) -+ this.coordinateOffset = this.encodeOffsetX + (this.encodeOffsetZ << COORDINATE_BITS); -+ -+ // need encoded values to be >= 0 -+ // so, 0 = (-SECTION_RADIUS + centerSectionX) + encodeOffset -+ this.encodeSectionOffsetX = SECTION_RADIUS - centerSectionX; -+ this.encodeSectionOffsetZ = SECTION_RADIUS - centerSectionZ; -+ -+ // section index = (secX + encodeSectionOffsetX) + ((secZ + encodeSectionOffsetZ) * SECTION_CACHE_WIDTH) -+ this.sectionIndexOffset = this.encodeSectionOffsetX + (this.encodeSectionOffsetZ * SECTION_CACHE_WIDTH); -+ } -+ -+ // must hold ticket lock for (centerSectionX,centerSectionZ) in radius rad -+ // must call setupEncodeOffset -+ protected final void setupCaches(final ThreadedTicketLevelPropagator propagator, -+ final int centerSectionX, final int centerSectionZ, -+ final int rad) { -+ for (int dz = -rad; dz <= rad; ++dz) { -+ for (int dx = -rad; dx <= rad; ++dx) { -+ final int sectionX = centerSectionX + dx; -+ final int sectionZ = centerSectionZ + dz; -+ final Coordinate coordinate = new Coordinate(sectionX, sectionZ); -+ final Section section = propagator.sections.get(coordinate); -+ -+ if (section == null) { -+ throw new IllegalStateException("Section at " + coordinate + " should not be null"); -+ } -+ -+ this.setSectionInCache(sectionX, sectionZ, section); -+ } -+ } -+ } -+ -+ protected final void setSectionInCache(final int sectionX, final int sectionZ, final Section section) { -+ this.sections[sectionX + SECTION_CACHE_WIDTH*sectionZ + this.sectionIndexOffset] = section; -+ } -+ -+ protected final Section getSection(final int sectionX, final int sectionZ) { -+ return this.sections[sectionX + SECTION_CACHE_WIDTH*sectionZ + this.sectionIndexOffset]; -+ } -+ -+ protected final int getLevel(final int posX, final int posZ) { -+ final Section section = this.sections[(posX >> SECTION_SHIFT) + SECTION_CACHE_WIDTH*(posZ >> SECTION_SHIFT) + this.sectionIndexOffset]; -+ if (section != null) { -+ return (int)section.levels[(posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT)] & 0xFF; -+ } -+ -+ return 0; -+ } -+ -+ protected final void setLevel(final int posX, final int posZ, final int to) { -+ final Section section = this.sections[(posX >> SECTION_SHIFT) + SECTION_CACHE_WIDTH*(posZ >> SECTION_SHIFT) + this.sectionIndexOffset]; -+ if (section != null) { -+ final int index = (posX & (SECTION_SIZE - 1)) | ((posZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); -+ final short level = section.levels[index]; -+ section.levels[index] = (short)((level & ~0xFF) | (to & 0xFF)); -+ this.updatedPositions.put(Coordinate.key(posX, posZ), (byte)to); -+ } -+ } -+ -+ protected final void destroyCaches() { -+ Arrays.fill(this.sections, null); -+ } -+ -+ // contains: -+ // lower (COORDINATE_BITS(9) + COORDINATE_BITS(9) = 18) bits encoded position: (x | (z << COORDINATE_BITS)) -+ // next LEVEL_BITS (6) bits: propagated level [0, 63] -+ // propagation directions bitset (16 bits): -+ protected static final long ALL_DIRECTIONS_BITSET = ( -+ // z = -1 -+ (1L << ((1 - 1) | ((1 - 1) << 2))) | -+ (1L << ((1 + 0) | ((1 - 1) << 2))) | -+ (1L << ((1 + 1) | ((1 - 1) << 2))) | -+ -+ // z = 0 -+ (1L << ((1 - 1) | ((1 + 0) << 2))) | -+ //(1L << ((1 + 0) | ((1 + 0) << 2))) | // exclude (0,0) -+ (1L << ((1 + 1) | ((1 + 0) << 2))) | -+ -+ // z = 1 -+ (1L << ((1 - 1) | ((1 + 1) << 2))) | -+ (1L << ((1 + 0) | ((1 + 1) << 2))) | -+ (1L << ((1 + 1) | ((1 + 1) << 2))) -+ ); -+ -+ private void ex(int bitset) { -+ for (int i = 0, len = Integer.bitCount(bitset); i < len; ++i) { -+ final int set = Integer.numberOfTrailingZeros(bitset); -+ final int tailingBit = (-bitset) & bitset; -+ // XOR to remove the trailing bit -+ bitset ^= tailingBit; -+ -+ // the encoded value set is (x_val) | (z_val << 2), totaling 4 bits -+ // thus, the bitset is 16 bits wide where each one represents a direction to propagate and the -+ // index of the set bit is the encoded value -+ // the encoded coordinate has 3 valid states: -+ // 0b00 (0) -> -1 -+ // 0b01 (1) -> 0 -+ // 0b10 (2) -> 1 -+ // the decode operation then is val - 1, and the encode operation is val + 1 -+ final int xOff = (set & 3) - 1; -+ final int zOff = ((set >>> 2) & 3) - 1; -+ System.out.println("Encoded: (" + xOff + "," + zOff + ")"); -+ } -+ } -+ -+ private void ch(long bs, int shift) { -+ int bitset = (int)(bs >>> shift); -+ for (int i = 0, len = Integer.bitCount(bitset); i < len; ++i) { -+ final int set = Integer.numberOfTrailingZeros(bitset); -+ final int tailingBit = (-bitset) & bitset; -+ // XOR to remove the trailing bit -+ bitset ^= tailingBit; -+ -+ // the encoded value set is (x_val) | (z_val << 2), totaling 4 bits -+ // thus, the bitset is 16 bits wide where each one represents a direction to propagate and the -+ // index of the set bit is the encoded value -+ // the encoded coordinate has 3 valid states: -+ // 0b00 (0) -> -1 -+ // 0b01 (1) -> 0 -+ // 0b10 (2) -> 1 -+ // the decode operation then is val - 1, and the encode operation is val + 1 -+ final int xOff = (set & 3) - 1; -+ final int zOff = ((set >>> 2) & 3) - 1; -+ if (Math.abs(xOff) > 1 || Math.abs(zOff) > 1 || (xOff | zOff) == 0) { -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ -+ // whether the increase propagator needs to write the propagated level to the position, used to avoid cascading -+ // updates for sources -+ protected static final long FLAG_WRITE_LEVEL = Long.MIN_VALUE >>> 1; -+ // whether the propagation needs to check if its current level is equal to the expected level -+ // used only in increase propagation -+ protected static final long FLAG_RECHECK_LEVEL = Long.MIN_VALUE >>> 0; -+ -+ protected long[] increaseQueue = new long[SECTION_SIZE * SECTION_SIZE * 2]; -+ protected int increaseQueueInitialLength; -+ protected long[] decreaseQueue = new long[SECTION_SIZE * SECTION_SIZE * 2]; -+ protected int decreaseQueueInitialLength; -+ -+ protected final Long2ByteLinkedOpenHashMap updatedPositions = new Long2ByteLinkedOpenHashMap(); -+ -+ protected final long[] resizeIncreaseQueue() { -+ return this.increaseQueue = Arrays.copyOf(this.increaseQueue, this.increaseQueue.length * 2); -+ } -+ -+ protected final long[] resizeDecreaseQueue() { -+ return this.decreaseQueue = Arrays.copyOf(this.decreaseQueue, this.decreaseQueue.length * 2); -+ } -+ -+ protected final void appendToIncreaseQueue(final long value) { -+ final int idx = this.increaseQueueInitialLength++; -+ long[] queue = this.increaseQueue; -+ if (idx >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ queue[idx] = value; -+ return; -+ } else { -+ queue[idx] = value; -+ return; -+ } -+ } -+ -+ protected final void appendToDecreaseQueue(final long value) { -+ final int idx = this.decreaseQueueInitialLength++; -+ long[] queue = this.decreaseQueue; -+ if (idx >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ queue[idx] = value; -+ return; -+ } else { -+ queue[idx] = value; -+ return; -+ } -+ } -+ -+ protected final void performIncrease() { -+ long[] queue = this.increaseQueue; -+ int queueReadIndex = 0; -+ int queueLength = this.increaseQueueInitialLength; -+ this.increaseQueueInitialLength = 0; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ final int encodeOffset = this.coordinateOffset; -+ final int sectionOffset = this.sectionIndexOffset; -+ -+ final Long2ByteLinkedOpenHashMap updatedPositions = this.updatedPositions; -+ -+ while (queueReadIndex < queueLength) { -+ final long queueValue = queue[queueReadIndex++]; -+ -+ final int posX = ((int)queueValue & (COORDINATE_SIZE - 1)) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> COORDINATE_BITS) & (COORDINATE_SIZE - 1)) + decodeOffsetZ; -+ final int propagatedLevel = ((int)queueValue >>> (COORDINATE_BITS + COORDINATE_BITS)) & (LEVEL_COUNT - 1); -+ // note: the above code requires coordinate bits * 2 < 32 -+ // bitset is 16 bits -+ int propagateDirectionBitset = (int)(queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); -+ -+ if ((queueValue & FLAG_RECHECK_LEVEL) != 0L) { -+ if (this.getLevel(posX, posZ) != propagatedLevel) { -+ // not at the level we expect, so something changed. -+ continue; -+ } -+ } else if ((queueValue & FLAG_WRITE_LEVEL) != 0L) { -+ // these are used to restore sources after a propagation decrease -+ this.setLevel(posX, posZ, propagatedLevel); -+ } -+ -+ // this bitset represents the values that we have not propagated to -+ // this bitset lets us determine what directions the neighbours we set should propagate to, in most cases -+ // significantly reducing the total number of ops -+ // since we propagate in a 1 radius, we need a 2 radius bitset to hold all possible values we would possibly need -+ // but if we use only 5x5 bits, then we need to use div/mod to retrieve coordinates from the bitset, so instead -+ // we use an 8x8 bitset and luckily that can be fit into only one long value (64 bits) -+ // to make things easy, we use positions [0, 4] in the bitset, with current position being 2 -+ // index = x | (z << 3) -+ -+ // to start, we eliminate everything 1 radius from the current position as the previous propagator -+ // must guarantee that either we propagate everything in 1 radius or we partially propagate for 1 radius -+ // but the rest not propagated are already handled -+ long currentPropagation = ~( -+ // z = -1 -+ (1L << ((2 - 1) | ((2 - 1) << 3))) | -+ (1L << ((2 + 0) | ((2 - 1) << 3))) | -+ (1L << ((2 + 1) | ((2 - 1) << 3))) | -+ -+ // z = 0 -+ (1L << ((2 - 1) | ((2 + 0) << 3))) | -+ (1L << ((2 + 0) | ((2 + 0) << 3))) | -+ (1L << ((2 + 1) | ((2 + 0) << 3))) | -+ -+ // z = 1 -+ (1L << ((2 - 1) | ((2 + 1) << 3))) | -+ (1L << ((2 + 0) | ((2 + 1) << 3))) | -+ (1L << ((2 + 1) | ((2 + 1) << 3))) -+ ); -+ -+ final int toPropagate = propagatedLevel - 1; -+ -+ // we could use while (propagateDirectionBitset != 0), but it's not a predictable branch. By counting -+ // the bits, the cpu loop predictor should perfectly predict the loop. -+ for (int l = 0, len = Integer.bitCount(propagateDirectionBitset); l < len; ++l) { -+ final int set = Integer.numberOfTrailingZeros(propagateDirectionBitset); -+ final int tailingBit = (-propagateDirectionBitset) & propagateDirectionBitset; -+ propagateDirectionBitset ^= tailingBit; -+ -+ // pDecode is from [0, 2], and 1 must be subtracted to fully decode the offset -+ // it has been split to save some cycles via parallelism -+ final int pDecodeX = (set & 3); -+ final int pDecodeZ = ((set >>> 2) & 3); -+ -+ // re-ordered -1 on the position decode into pos - 1 to occur in parallel with determining pDecodeX -+ final int offX = (posX - 1) + pDecodeX; -+ final int offZ = (posZ - 1) + pDecodeZ; -+ -+ final int sectionIndex = (offX >> SECTION_SHIFT) + ((offZ >> SECTION_SHIFT) * SECTION_CACHE_WIDTH) + sectionOffset; -+ final int localIndex = (offX & (SECTION_SIZE - 1)) | ((offZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); -+ -+ // to retrieve a set of bits from a long value: (n_bitmask << (nstartidx)) & bitset -+ // bitset idx = x | (z << 3) -+ -+ // read three bits, so we need 7L -+ // note that generally: off - pos = (pos - 1) + pDecode - pos = pDecode - 1 -+ // nstartidx1 = x rel -1 for z rel -1 -+ // = (offX - posX - 1 + 2) | ((offZ - posZ - 1 + 2) << 3) -+ // = (pDecodeX - 1 - 1 + 2) | ((pDecodeZ - 1 - 1 + 2) << 3) -+ // = pDecodeX | (pDecodeZ << 3) = start -+ final int start = pDecodeX | (pDecodeZ << 3); -+ final long bitsetLine1 = currentPropagation & (7L << (start)); -+ -+ // nstartidx2 = x rel -1 for z rel 0 = line after line1, so we can just add 8 (row length of bitset) -+ final long bitsetLine2 = currentPropagation & (7L << (start + 8)); -+ -+ // nstartidx2 = x rel -1 for z rel 0 = line after line2, so we can just add 8 (row length of bitset) -+ final long bitsetLine3 = currentPropagation & (7L << (start + (8 + 8))); -+ -+ // remove ("take") lines from bitset -+ currentPropagation ^= (bitsetLine1 | bitsetLine2 | bitsetLine3); -+ -+ // now try to propagate -+ final Section section = this.sections[sectionIndex]; -+ -+ // lower 8 bits are current level, next upper 7 bits are source level, next 1 bit is updated source flag -+ final short currentStoredLevel = section.levels[localIndex]; -+ final int currentLevel = currentStoredLevel & 0xFF; -+ -+ if (currentLevel >= toPropagate) { -+ continue; // already at the level we want -+ } -+ -+ // update level -+ section.levels[localIndex] = (short)((currentStoredLevel & ~0xFF) | (toPropagate & 0xFF)); -+ updatedPositions.putAndMoveToLast(Coordinate.key(offX, offZ), (byte)toPropagate); -+ -+ // queue next -+ if (toPropagate > 1) { -+ // now combine into one bitset to pass to child -+ // the child bitset is 4x4, so we just shift each line by 4 -+ // add the propagation bitset offset to each line to make it easy to OR it into the propagation queue value -+ final long childPropagation = -+ ((bitsetLine1 >>> (start)) << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = -1 -+ ((bitsetLine2 >>> (start + 8)) << (4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = 0 -+ ((bitsetLine3 >>> (start + (8 + 8))) << (4 + 4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); // z = 1 -+ -+ // don't queue update if toPropagate cannot propagate anything to neighbours -+ // (for increase, propagating 0 to neighbours is useless) -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | -+ ((toPropagate & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | -+ childPropagation; //(ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); -+ continue; -+ } -+ continue; -+ } -+ } -+ } -+ -+ protected final void performDecrease() { -+ long[] queue = this.decreaseQueue; -+ long[] increaseQueue = this.increaseQueue; -+ int queueReadIndex = 0; -+ int queueLength = this.decreaseQueueInitialLength; -+ this.decreaseQueueInitialLength = 0; -+ int increaseQueueLength = this.increaseQueueInitialLength; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ final int encodeOffset = this.coordinateOffset; -+ final int sectionOffset = this.sectionIndexOffset; -+ -+ final Long2ByteLinkedOpenHashMap updatedPositions = this.updatedPositions; -+ -+ while (queueReadIndex < queueLength) { -+ final long queueValue = queue[queueReadIndex++]; -+ -+ final int posX = ((int)queueValue & (COORDINATE_SIZE - 1)) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> COORDINATE_BITS) & (COORDINATE_SIZE - 1)) + decodeOffsetZ; -+ final int propagatedLevel = ((int)queueValue >>> (COORDINATE_BITS + COORDINATE_BITS)) & (LEVEL_COUNT - 1); -+ // note: the above code requires coordinate bits * 2 < 32 -+ // bitset is 16 bits -+ int propagateDirectionBitset = (int)(queueValue >>> (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) & ((1 << 16) - 1); -+ -+ // this bitset represents the values that we have not propagated to -+ // this bitset lets us determine what directions the neighbours we set should propagate to, in most cases -+ // significantly reducing the total number of ops -+ // since we propagate in a 1 radius, we need a 2 radius bitset to hold all possible values we would possibly need -+ // but if we use only 5x5 bits, then we need to use div/mod to retrieve coordinates from the bitset, so instead -+ // we use an 8x8 bitset and luckily that can be fit into only one long value (64 bits) -+ // to make things easy, we use positions [0, 4] in the bitset, with current position being 2 -+ // index = x | (z << 3) -+ -+ // to start, we eliminate everything 1 radius from the current position as the previous propagator -+ // must guarantee that either we propagate everything in 1 radius or we partially propagate for 1 radius -+ // but the rest not propagated are already handled -+ long currentPropagation = ~( -+ // z = -1 -+ (1L << ((2 - 1) | ((2 - 1) << 3))) | -+ (1L << ((2 + 0) | ((2 - 1) << 3))) | -+ (1L << ((2 + 1) | ((2 - 1) << 3))) | -+ -+ // z = 0 -+ (1L << ((2 - 1) | ((2 + 0) << 3))) | -+ (1L << ((2 + 0) | ((2 + 0) << 3))) | -+ (1L << ((2 + 1) | ((2 + 0) << 3))) | -+ -+ // z = 1 -+ (1L << ((2 - 1) | ((2 + 1) << 3))) | -+ (1L << ((2 + 0) | ((2 + 1) << 3))) | -+ (1L << ((2 + 1) | ((2 + 1) << 3))) -+ ); -+ -+ final int toPropagate = propagatedLevel - 1; -+ -+ // we could use while (propagateDirectionBitset != 0), but it's not a predictable branch. By counting -+ // the bits, the cpu loop predictor should perfectly predict the loop. -+ for (int l = 0, len = Integer.bitCount(propagateDirectionBitset); l < len; ++l) { -+ final int set = Integer.numberOfTrailingZeros(propagateDirectionBitset); -+ final int tailingBit = (-propagateDirectionBitset) & propagateDirectionBitset; -+ propagateDirectionBitset ^= tailingBit; -+ -+ -+ // pDecode is from [0, 2], and 1 must be subtracted to fully decode the offset -+ // it has been split to save some cycles via parallelism -+ final int pDecodeX = (set & 3); -+ final int pDecodeZ = ((set >>> 2) & 3); -+ -+ // re-ordered -1 on the position decode into pos - 1 to occur in parallel with determining pDecodeX -+ final int offX = (posX - 1) + pDecodeX; -+ final int offZ = (posZ - 1) + pDecodeZ; -+ -+ final int sectionIndex = (offX >> SECTION_SHIFT) + ((offZ >> SECTION_SHIFT) * SECTION_CACHE_WIDTH) + sectionOffset; -+ final int localIndex = (offX & (SECTION_SIZE - 1)) | ((offZ & (SECTION_SIZE - 1)) << SECTION_SHIFT); -+ -+ // to retrieve a set of bits from a long value: (n_bitmask << (nstartidx)) & bitset -+ // bitset idx = x | (z << 3) -+ -+ // read three bits, so we need 7L -+ // note that generally: off - pos = (pos - 1) + pDecode - pos = pDecode - 1 -+ // nstartidx1 = x rel -1 for z rel -1 -+ // = (offX - posX - 1 + 2) | ((offZ - posZ - 1 + 2) << 3) -+ // = (pDecodeX - 1 - 1 + 2) | ((pDecodeZ - 1 - 1 + 2) << 3) -+ // = pDecodeX | (pDecodeZ << 3) = start -+ final int start = pDecodeX | (pDecodeZ << 3); -+ final long bitsetLine1 = currentPropagation & (7L << (start)); -+ -+ // nstartidx2 = x rel -1 for z rel 0 = line after line1, so we can just add 8 (row length of bitset) -+ final long bitsetLine2 = currentPropagation & (7L << (start + 8)); -+ -+ // nstartidx2 = x rel -1 for z rel 0 = line after line2, so we can just add 8 (row length of bitset) -+ final long bitsetLine3 = currentPropagation & (7L << (start + (8 + 8))); -+ -+ // now try to propagate -+ final Section section = this.sections[sectionIndex]; -+ -+ // lower 8 bits are current level, next upper 7 bits are source level, next 1 bit is updated source flag -+ final short currentStoredLevel = section.levels[localIndex]; -+ final int currentLevel = currentStoredLevel & 0xFF; -+ final int sourceLevel = (currentStoredLevel >>> 8) & 0xFF; -+ -+ if (currentLevel == 0) { -+ continue; // already at the level we want -+ } -+ -+ if (currentLevel > toPropagate) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | -+ ((currentLevel & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | -+ (FLAG_RECHECK_LEVEL | (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS))); -+ continue; -+ } -+ -+ // remove ("take") lines from bitset -+ // can't do this during decrease, TODO WHY? -+ //currentPropagation ^= (bitsetLine1 | bitsetLine2 | bitsetLine3); -+ -+ // update level -+ section.levels[localIndex] = (short)((currentStoredLevel & ~0xFF)); -+ updatedPositions.putAndMoveToLast(Coordinate.key(offX, offZ), (byte)0); -+ -+ if (sourceLevel != 0) { -+ // re-propagate source -+ // note: do not set recheck level, or else the propagation will fail -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | -+ ((sourceLevel & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | -+ (FLAG_WRITE_LEVEL | (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS))); -+ } -+ -+ // queue next -+ // note: targetLevel > 0 here, since toPropagate >= currentLevel and currentLevel > 0 -+ // now combine into one bitset to pass to child -+ // the child bitset is 4x4, so we just shift each line by 4 -+ // add the propagation bitset offset to each line to make it easy to OR it into the propagation queue value -+ final long childPropagation = -+ ((bitsetLine1 >>> (start)) << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = -1 -+ ((bitsetLine2 >>> (start + 8)) << (4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)) | // z = 0 -+ ((bitsetLine3 >>> (start + (8 + 8))) << (4 + 4 + COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); // z = 1 -+ -+ // don't queue update if toPropagate cannot propagate anything to neighbours -+ // (for increase, propagating 0 to neighbours is useless) -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((long)(offX + (offZ << COORDINATE_BITS) + encodeOffset) & ((1L << (COORDINATE_BITS + COORDINATE_BITS)) - 1)) | -+ ((toPropagate & (LEVEL_COUNT - 1L)) << (COORDINATE_BITS + COORDINATE_BITS)) | -+ (ALL_DIRECTIONS_BITSET << (COORDINATE_BITS + COORDINATE_BITS + LEVEL_BITS)); //childPropagation; -+ continue; -+ } -+ } -+ -+ // propagate sources we clobbered -+ this.increaseQueueInitialLength = increaseQueueLength; -+ this.performIncrease(); -+ } -+ } -+ -+ private static final class Coordinate implements Comparable { -+ -+ public final long key; -+ -+ public Coordinate(final long key) { -+ this.key = key; -+ } -+ -+ public Coordinate(final int x, final int z) { -+ this.key = key(x, z); -+ } -+ -+ public static long key(final int x, final int z) { -+ return ((long)z << 32) | (x & 0xFFFFFFFFL); -+ } -+ -+ public static int x(final long key) { -+ return (int)key; -+ } -+ -+ public static int z(final long key) { -+ return (int)(key >>> 32); -+ } -+ -+ @Override -+ public int hashCode() { -+ return (int)HashCommon.mix(this.key); -+ } -+ -+ @Override -+ public boolean equals(final Object obj) { -+ if (this == obj) { -+ return true; -+ } -+ -+ if (!(obj instanceof Coordinate other)) { -+ return false; -+ } -+ -+ return this.key == other.key; -+ } -+ -+ // This class is intended for HashMap/ConcurrentHashMap usage, which do treeify bin nodes if the chain -+ // is too large. So we should implement compareTo to help. -+ @Override -+ public int compareTo(final Coordinate other) { -+ return Long.compare(this.key, other.key); -+ } -+ -+ @Override -+ public String toString() { -+ return "[" + x(this.key) + "," + z(this.key) + "]"; -+ } -+ } -+ -+ /* -+ private static final java.util.Random random = new java.util.Random(4L); -+ private static final List> walkers = -+ new java.util.ArrayList<>(); -+ static final int PLAYERS = 0; -+ static final int RAD_BLOCKS = 10000; -+ static final int RAD = RAD_BLOCKS >> 4; -+ static final int RAD_BIG_BLOCKS = 100_000; -+ static final int RAD_BIG = RAD_BIG_BLOCKS >> 4; -+ static final int VD = 4; -+ static final int BIG_PLAYERS = 50; -+ static final double WALK_CHANCE = 0.10; -+ static final double TP_CHANCE = 0.01; -+ static final int TP_BACK_PLAYERS = 200; -+ static final double TP_BACK_CHANCE = 0.25; -+ static final double TP_STEAL_CHANCE = 0.25; -+ private static final List> tpBack = -+ new java.util.ArrayList<>(); -+ -+ public static void main(final String[] args) { -+ final ReentrantAreaLock ticketLock = new ReentrantAreaLock(SECTION_SHIFT); -+ final ReentrantAreaLock schedulingLock = new ReentrantAreaLock(SECTION_SHIFT); -+ final Long2ByteLinkedOpenHashMap levelMap = new Long2ByteLinkedOpenHashMap(); -+ final Long2ByteLinkedOpenHashMap refMap = new Long2ByteLinkedOpenHashMap(); -+ final io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D ref = new io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D((final long coordinate, final byte oldLevel, final byte newLevel) -> { -+ if (newLevel == 0) { -+ refMap.remove(coordinate); -+ } else { -+ refMap.put(coordinate, newLevel); -+ } -+ }); -+ final ThreadedTicketLevelPropagator propagator = new ThreadedTicketLevelPropagator() { -+ @Override -+ protected void processLevelUpdates(Long2ByteLinkedOpenHashMap updates) { -+ for (final long key : updates.keySet()) { -+ final byte val = updates.get(key); -+ if (val == 0) { -+ levelMap.remove(key); -+ } else { -+ levelMap.put(key, val); -+ } -+ } -+ } -+ -+ @Override -+ protected void processSchedulingUpdates(Long2ByteLinkedOpenHashMap updates, List scheduledTasks, List changedFullStatus) {} -+ }; -+ -+ for (;;) { -+ if (walkers.isEmpty() && tpBack.isEmpty()) { -+ for (int i = 0; i < PLAYERS; ++i) { -+ int rad = i < BIG_PLAYERS ? RAD_BIG : RAD; -+ int posX = random.nextInt(-rad, rad + 1); -+ int posZ = random.nextInt(-rad, rad + 1); -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap<>(null) { -+ @Override -+ protected void addCallback(Void parameter, int chunkX, int chunkZ) { -+ int src = 45 - 31 + 1; -+ ref.setSource(chunkX, chunkZ, src); -+ propagator.setSource(chunkX, chunkZ, src); -+ } -+ -+ @Override -+ protected void removeCallback(Void parameter, int chunkX, int chunkZ) { -+ ref.removeSource(chunkX, chunkZ); -+ propagator.removeSource(chunkX, chunkZ); -+ } -+ }; -+ -+ map.add(posX, posZ, VD); -+ -+ walkers.add(map); -+ } -+ for (int i = 0; i < TP_BACK_PLAYERS; ++i) { -+ int rad = RAD_BIG; -+ int posX = random.nextInt(-rad, rad + 1); -+ int posZ = random.nextInt(-rad, rad + 1); -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap<>(null) { -+ @Override -+ protected void addCallback(Void parameter, int chunkX, int chunkZ) { -+ int src = 45 - 31 + 1; -+ ref.setSource(chunkX, chunkZ, src); -+ propagator.setSource(chunkX, chunkZ, src); -+ } -+ -+ @Override -+ protected void removeCallback(Void parameter, int chunkX, int chunkZ) { -+ ref.removeSource(chunkX, chunkZ); -+ propagator.removeSource(chunkX, chunkZ); -+ } -+ }; -+ -+ map.add(posX, posZ, random.nextInt(1, 63)); -+ -+ tpBack.add(map); -+ } -+ } else { -+ for (int i = 0; i < PLAYERS; ++i) { -+ if (random.nextDouble() > WALK_CHANCE) { -+ continue; -+ } -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = walkers.get(i); -+ -+ int updateX = random.nextInt(-1, 2); -+ int updateZ = random.nextInt(-1, 2); -+ -+ map.update(map.lastChunkX + updateX, map.lastChunkZ + updateZ, VD); -+ } -+ -+ for (int i = 0; i < PLAYERS; ++i) { -+ if (random.nextDouble() > TP_CHANCE) { -+ continue; -+ } -+ -+ int rad = i < BIG_PLAYERS ? RAD_BIG : RAD; -+ int posX = random.nextInt(-rad, rad + 1); -+ int posZ = random.nextInt(-rad, rad + 1); -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = walkers.get(i); -+ -+ map.update(posX, posZ, VD); -+ } -+ -+ for (int i = 0; i < TP_BACK_PLAYERS; ++i) { -+ if (random.nextDouble() > TP_BACK_CHANCE) { -+ continue; -+ } -+ -+ io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.SingleUserAreaMap map = tpBack.get(i); -+ -+ map.update(-map.lastChunkX, -map.lastChunkZ, random.nextInt(1, 63)); -+ -+ if (random.nextDouble() > TP_STEAL_CHANCE) { -+ propagator.performUpdate( -+ map.lastChunkX >> SECTION_SHIFT, map.lastChunkZ >> SECTION_SHIFT, schedulingLock, null, null -+ ); -+ propagator.performUpdate( -+ (-map.lastChunkX >> SECTION_SHIFT), (-map.lastChunkZ >> SECTION_SHIFT), schedulingLock, null, null -+ ); -+ } -+ } -+ } -+ -+ ref.propagateUpdates(); -+ propagator.performUpdates(ticketLock, schedulingLock, null, null); -+ -+ if (!refMap.equals(levelMap)) { -+ throw new IllegalStateException("Error!"); -+ } -+ } -+ } -+ */ -+} -diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java -index 768a2667f950a635a562fa8a0c75b31a3ae9190e..6b727f452ae8461edc0d734173f25817af1e3318 100644 ---- a/src/main/java/net/minecraft/server/level/Ticket.java -+++ b/src/main/java/net/minecraft/server/level/Ticket.java -@@ -7,10 +7,12 @@ public final class Ticket implements Comparable> { - private final int ticketLevel; - public final T key; - // Paper start - rewrite chunk system -- public final long removalTick; -+ // Folia start - use area based lock to reduce contention -+ public long removeDelay; - -- public Ticket(TicketType type, int level, T argument, long removalTick) { -- this.removalTick = removalTick; -+ public Ticket(TicketType type, int level, T argument, long removeDelay) { -+ this.removeDelay = removeDelay; -+ // Folia end - use area based lock to reduce contention - // Paper end - rewrite chunk system - this.type = type; - this.ticketLevel = level; -@@ -47,7 +49,7 @@ public final class Ticket implements Comparable> { - - @Override - public String toString() { -- return "Ticket[" + this.type + " " + this.ticketLevel + " (" + this.key + ")] to die on " + this.removalTick; // Paper - rewrite chunk system -+ return "Ticket[" + this.type + " " + this.ticketLevel + " (" + this.key + ")] to die in " + this.removeDelay; // Paper - rewrite chunk system // Folia - use area based lock to reduce contention - } - - public TicketType getType() { -diff --git a/src/main/java/net/minecraft/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java -index d227b91defc3992f1a003a19264bc3aa29718795..2aee6ae5d588f36fe23ffd2a88a5a2a925b52b8e 100644 ---- a/src/main/java/net/minecraft/util/SortedArraySet.java -+++ b/src/main/java/net/minecraft/util/SortedArraySet.java -@@ -14,6 +14,14 @@ public class SortedArraySet extends AbstractSet { - T[] contents; - int size; - -+ // Folia start - use area based lock to reduce contention -+ public SortedArraySet(final SortedArraySet other) { -+ this.comparator = other.comparator; -+ this.size = other.size; -+ this.contents = Arrays.copyOf(other.contents, this.size); -+ } -+ // Folia end - use area based lock to reduce contention -+ - private SortedArraySet(int initialCapacity, Comparator comparator) { - this.comparator = comparator; - if (initialCapacity < 0) { diff --git a/patches/server/0024-Mark-POI-Entity-load-tasks-as-completed-before-relea.patch b/patches/server/0024-Mark-POI-Entity-load-tasks-as-completed-before-relea.patch deleted file mode 100644 index 324d94e..0000000 --- a/patches/server/0024-Mark-POI-Entity-load-tasks-as-completed-before-relea.patch +++ /dev/null @@ -1,110 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 15 May 2023 11:34:28 -0700 -Subject: [PATCH] Mark POI/Entity load tasks as completed before releasing - scheduling lock - -It must be marked as completed during that lock hold since the -waiters field is set to null. Thus, any other thread attempting -a cancellation will fail to remove from waiters. Also, any -other thread attempting to cancel may set the completed field -to true which would cause accept() to fail as well. - -Completion was always designed to happen while holding the -scheduling lock to prevent these race conditions. The code -was originally set up to complete while not holding the -scheduling lock to avoid invoking callbacks while holding the -lock, however the access to the completion field was not -considered. - -Resolve this by marking the callback as completed during the -lock, but invoking the accept() function after releasing -the lock. This will prevent any cancellation attempts to be -blocked, and allow the current thread to complete the callback -without any issues. - -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java -index 1ff6b138ccf4a1cefa719cd0b2b3af02d18a26fb..dc60f4af918c2bd73873cadfb69e5572173b8e46 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java -@@ -156,6 +156,12 @@ public final class NewChunkHolder { - LOGGER.error("Unhandled entity data load exception, data data will be lost: ", result.right()); - } - -+ // Folia start - mark these tasks as completed before releasing the scheduling lock -+ for (final GenericDataLoadTaskCallback callback : waiters) { -+ callback.markCompleted(); -+ } -+ // Folia end - mark these tasks as completed before releasing the scheduling lock -+ - completeWaiters = waiters; - } else { - // cancelled -@@ -187,7 +193,7 @@ public final class NewChunkHolder { - // avoid holding the scheduling lock while completing - if (completeWaiters != null) { - for (final GenericDataLoadTaskCallback callback : completeWaiters) { -- callback.accept(result); -+ callback.acceptCompleted(result); // Folia - mark these tasks as completed before releasing the scheduling lock - } - } - -@@ -273,6 +279,12 @@ public final class NewChunkHolder { - LOGGER.error("Unhandled poi load exception, poi data will be lost: ", result.right()); - } - -+ // Folia start - mark these tasks as completed before releasing the scheduling lock -+ for (final GenericDataLoadTaskCallback callback : waiters) { -+ callback.markCompleted(); -+ } -+ // Folia end - mark these tasks as completed before releasing the scheduling lock -+ - completeWaiters = waiters; - } else { - // cancelled -@@ -304,7 +316,7 @@ public final class NewChunkHolder { - // avoid holding the scheduling lock while completing - if (completeWaiters != null) { - for (final GenericDataLoadTaskCallback callback : completeWaiters) { -- callback.accept(result); -+ callback.acceptCompleted(result); // Folia - mark these tasks as completed before releasing the scheduling lock - } - } - schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); // Folia - use area based lock to reduce contention -@@ -357,7 +369,7 @@ public final class NewChunkHolder { - } - } - -- public static abstract class GenericDataLoadTaskCallback implements Cancellable, Consumer> { -+ public static abstract class GenericDataLoadTaskCallback implements Cancellable { // Folia - mark callbacks as completed before unlocking scheduling lock - - protected final Consumer> consumer; - protected final NewChunkHolder chunkHolder; -@@ -393,13 +405,23 @@ public final class NewChunkHolder { - return this.completed = true; - } - -- @Override -- public void accept(final GenericDataLoadTask.TaskResult result) { -+ // Folia start - mark callbacks as completed before unlocking scheduling lock -+ // must hold scheduling lock -+ void markCompleted() { -+ if (this.completed) { -+ throw new IllegalStateException("May not be completed here"); -+ } -+ this.completed = true; -+ } -+ // Folia end - mark callbacks as completed before unlocking scheduling lock -+ -+ // Folia - mark callbacks as completed before unlocking scheduling lock -+ void acceptCompleted(final GenericDataLoadTask.TaskResult result) { - if (result != null) { -- if (this.setCompleted()) { -+ if (this.completed) { // Folia - mark callbacks as completed before unlocking scheduling lock - this.consumer.accept(result); - } else { -- throw new IllegalStateException("Cannot be cancelled at this point"); -+ throw new IllegalStateException("Cannot be uncompleted at this point"); // Folia - mark callbacks as completed before unlocking scheduling lock - } - } else { - throw new NullPointerException("Result cannot be null (cancelled)"); diff --git a/patches/server/0025-Properly-cancel-chunk-load-tasks-that-were-not-sched.patch b/patches/server/0025-Properly-cancel-chunk-load-tasks-that-were-not-sched.patch deleted file mode 100644 index e7f7230..0000000 --- a/patches/server/0025-Properly-cancel-chunk-load-tasks-that-were-not-sched.patch +++ /dev/null @@ -1,63 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 15 May 2023 12:24:17 -0700 -Subject: [PATCH] Properly cancel chunk load tasks that were not scheduled - -Since the chunk load task was not scheduled, the entity/poi load -task fields will not be set, but the task complete counter -will not be adjusted. Thus, the chunk load task will not complete. - -To resolve this, detect when the entity/poi tasks were not scheduled -and decrement the task complete counter in such cases. - -diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java -index 7b8362625b48f1829ed4fd3c7fde6a4bec8e4099..be53c4b4a10463ef27f6fa178f17f92ca866e2e6 100644 ---- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java -+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkLoadTask.java -@@ -126,7 +126,9 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - public void cancel() { - // must be before load task access, so we can synchronise with the writes to the fields - final ca.spottedleaf.concurrentutil.lock.ReentrantAreaLock.Node schedulingLock = this.scheduler.schedulingLockArea.lock(this.chunkX, this.chunkZ); // Folia - use area based lock to reduce contention -+ final boolean scheduled; // Folia - fix cancellation of chunk load task - try { -+ scheduled = this.scheduled; // Folia - fix cancellation of chunk load task - must read field here, as it may be written later conucrrently - we need to know if we scheduled _before_ cancellation - this.cancelled = true; - } finally { - this.scheduler.schedulingLockArea.unlock(schedulingLock); // Folia - use area based lock to reduce contention -@@ -139,16 +141,29 @@ public final class ChunkLoadTask extends ChunkProgressionTask { - the chunk load task attempts to complete with a non-null value - */ - -- if (this.entityLoadTask != null) { -- if (this.entityLoadTask.cancel()) { -- this.tryCompleteLoad(); -+ // Folia start - fix cancellation of chunk load task -+ if (scheduled) { -+ // since we scheduled, we need to cancel the tasks -+ if (this.entityLoadTask != null) { -+ if (this.entityLoadTask.cancel()) { -+ this.tryCompleteLoad(); -+ } - } -- } -- if (this.poiLoadTask != null) { -- if (this.poiLoadTask.cancel()) { -- this.tryCompleteLoad(); -+ if (this.poiLoadTask != null) { -+ if (this.poiLoadTask.cancel()) { -+ this.tryCompleteLoad(); -+ } - } -+ } else { -+ // since nothing was scheduled, we need to decrement the task count here ourselves -+ -+ // for entity load task -+ this.tryCompleteLoad(); -+ -+ // for poi load task -+ this.tryCompleteLoad(); - } -+ // Folia end - fix cancellation of chunk load task - this.loadTask.cancel(); - } - diff --git a/patches/server/0026-Always-recalculate-light-list-on-protochunk-deserial.patch b/patches/server/0026-Always-recalculate-light-list-on-protochunk-deserial.patch deleted file mode 100644 index c1fe2bc..0000000 --- a/patches/server/0026-Always-recalculate-light-list-on-protochunk-deserial.patch +++ /dev/null @@ -1,68 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Mon, 15 May 2023 20:31:36 -0700 -Subject: [PATCH] Always recalculate light list on protochunk deserialize - -I noticed during my stress testing that the total size of the -light list was far too large, which indicates many duplicates. -For me, this caused many GC problems which made stress testing -harder. - -It turns out, it was possible for the light list recalculation -logic to occur _and_ the addition of the light list data from -the NBT data. Since there is no logic to de-duplicate this list, -every chunk load would re-add all light sources into the light -list and the light list would grow uncontrollably. - -Since the recalculation logic would often run, I have -decided to solve this by discarding the data on disk and always -just calculating the list from the chunk data alone. Additionally, -I have applied an optimization from Vanilla 1.20 to avoid -searching sections without light sources by first checking the -palette for possible block sources. - -Now my stress tests do not have issues with GC at all. - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 3c9ae42290c2e0cb70bb08e97f3ea2c3fb594c7d..200915f5accdb26c634cc77300a7d3275a8b1e0a 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -321,7 +321,7 @@ public class ChunkSerializer { - BelowZeroRetrogen belowzeroretrogen = protochunk.getBelowZeroRetrogen(); - boolean flag5 = chunkstatus.isOrAfter(ChunkStatus.LIGHT) || belowzeroretrogen != null && belowzeroretrogen.targetStatus().isOrAfter(ChunkStatus.LIGHT); - -- if (!flag) { // Paper - fix incorrect parsing of blocks that emit light - it should always parse it, unless the chunk is marked as lit -+ if (true) { // Paper - fix incorrect parsing of blocks that emit light - it should always parse it, unless the chunk is marked as lit // Folia - always recalculate light data to eliminate duplicates - // Paper start - let's make sure the implementation isn't as slow as possible - int offX = chunkPos.x << 4; - int offZ = chunkPos.z << 4; -@@ -332,7 +332,7 @@ public class ChunkSerializer { - LevelChunkSection[] sections = achunksection; - for (int sectionY = minChunkSection; sectionY <= maxChunkSection; ++sectionY) { - LevelChunkSection section = sections[sectionY - minChunkSection]; -- if (section == null || section.hasOnlyAir()) { -+ if (section == null || section.hasOnlyAir() || !section.maybeHas((BlockState state) -> state.getLightEmission() > 0)) { // Folia - always recalculate light data to eliminate duplicates - skip sections that definitely have no sources - // no sources in empty sections - continue; - } -@@ -416,19 +416,7 @@ public class ChunkSerializer { - ((ChunkAccess) object1).setBlockEntityNbt(nbttagcompound4); - } - -- ListTag nbttaglist4 = nbt.getList("Lights", 9); -- -- for (int l1 = 0; l1 < nbttaglist4.size(); ++l1) { -- LevelChunkSection chunksection1 = achunksection[l1]; -- -- if (chunksection1 != null && !chunksection1.hasOnlyAir()) { -- ListTag nbttaglist5 = nbttaglist4.getList(l1); -- -- for (int i2 = 0; i2 < nbttaglist5.size(); ++i2) { -- protochunk1.addLight(nbttaglist5.getShort(i2), l1); -- } -- } -- } -+ // Folia - always recalculate light data to eliminate duplicates - - nbttagcompound4 = nbt.getCompound("CarvingMasks"); - Iterator iterator2 = nbttagcompound4.getAllKeys().iterator(); diff --git a/rb.bat b/rb.bat old mode 100644 new mode 100755