aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/unapplied
diff options
context:
space:
mode:
authorOwen1212055 <[email protected]>2024-04-25 08:36:48 -0400
committerOwen1212055 <[email protected]>2024-04-25 08:36:48 -0400
commit345b5c1c4c3f99756029f039b8e063ed6f8c3181 (patch)
treec229a0fd351d859a98e60ccce197b358a8a9fb62 /patches/unapplied
parent6da0d8cc91be3b8e1c84c0436f49654cdde6de2f (diff)
downloadPaper-345b5c1c4c3f99756029f039b8e063ed6f8c3181.tar.gz
Paper-345b5c1c4c3f99756029f039b8e063ed6f8c3181.zip
Patches!!!!!!! (we are done)
Diffstat (limited to 'patches/unapplied')
-rw-r--r--patches/unapplied/server/1042-Fix-and-optimise-world-force-upgrading.patch385
-rw-r--r--patches/unapplied/server/1068-API-for-checking-sent-chunks.patch62
2 files changed, 0 insertions, 447 deletions
diff --git a/patches/unapplied/server/1042-Fix-and-optimise-world-force-upgrading.patch b/patches/unapplied/server/1042-Fix-and-optimise-world-force-upgrading.patch
deleted file mode 100644
index de599d49f8..0000000000
--- a/patches/unapplied/server/1042-Fix-and-optimise-world-force-upgrading.patch
+++ /dev/null
@@ -1,385 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Thu, 20 May 2021 07:02:22 -0700
-Subject: [PATCH] Fix and optimise world force upgrading
-
-The WorldUpgrader class was incorrectly modified by
-CB. It will store an IChunkLoader instance for all
-dimension types in the world, but obviously with how
-CB shifts around worlds only one dimension type exists
-per world. But this would be OK if CB did this
-change correctly. All IChunkLoader instances
-will point to the same regionfiles. And all
-IChunkLoader instances are going to be read from.
-
-This problem hasn't really been reported because
-it relies on the persistent legacy data to be converted
-as well to cause corruption. Why? Because the legacy
-data is also shared, it will result in different
-outputs from conversion (as once conversion for legacy
-persistent data takes place, it is REMOVED - so the next
-convert will _not_ have the data). Which means different
-sizes on disk. Which means different regionfile sector
-allocations. Which means there are 3 different possible
-regionfile sector allocations in memory, and none of them
-are going to be correct.
-
-I've fixed this by writing a world upgrader suited to
-CB's changes to world folder format. It was brain dead
-easy to add threading, so I did.
-
-== AT ==
-public net.minecraft.util.worldupdate.WorldUpgrader REGEX
-
-diff --git a/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..90498d9a4f5aee0f6c8a202b5580b4260a28b378
---- /dev/null
-+++ b/src/main/java/io/papermc/paper/world/ThreadedWorldUpgrader.java
-@@ -0,0 +1,212 @@
-+package io.papermc.paper.world;
-+
-+import com.mojang.datafixers.DataFixer;
-+import com.mojang.serialization.Codec;
-+import net.minecraft.SharedConstants;
-+import net.minecraft.nbt.CompoundTag;
-+import net.minecraft.resources.ResourceKey;
-+import net.minecraft.util.worldupdate.WorldUpgrader;
-+import net.minecraft.world.level.ChunkPos;
-+import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.chunk.ChunkGenerator;
-+import net.minecraft.world.level.chunk.storage.ChunkStorage;
-+import net.minecraft.world.level.chunk.storage.RegionFileStorage;
-+import net.minecraft.world.level.dimension.DimensionType;
-+import net.minecraft.world.level.dimension.LevelStem;
-+import net.minecraft.world.level.levelgen.WorldGenSettings;
-+import net.minecraft.world.level.storage.DimensionDataStorage;
-+import net.minecraft.world.level.storage.LevelStorageSource;
-+import org.apache.logging.log4j.LogManager;
-+import org.apache.logging.log4j.Logger;
-+import java.io.File;
-+import java.io.IOException;
-+import java.text.DecimalFormat;
-+import java.util.Optional;
-+import java.util.concurrent.ExecutorService;
-+import java.util.concurrent.Executors;
-+import java.util.concurrent.ThreadFactory;
-+import java.util.concurrent.atomic.AtomicInteger;
-+import java.util.concurrent.atomic.AtomicLong;
-+import java.util.function.Supplier;
-+
-+public class ThreadedWorldUpgrader {
-+
-+ private static final Logger LOGGER = LogManager.getLogger();
-+
-+ private final ResourceKey<LevelStem> dimensionType;
-+ private final String worldName;
-+ private final File worldDir;
-+ private final ExecutorService threadPool;
-+ private final DataFixer dataFixer;
-+ private final Optional<ResourceKey<Codec<? extends ChunkGenerator>>> generatorKey;
-+ private final boolean removeCaches;
-+ private final boolean recreateRegionFiles; // TODO
-+
-+ public ThreadedWorldUpgrader(final ResourceKey<LevelStem> dimensionType, final String worldName, final File worldDir, final int threads,
-+ final DataFixer dataFixer, final Optional<ResourceKey<Codec<? extends ChunkGenerator>>> generatorKey,
-+ final boolean removeCaches, final boolean recreateRegionFiles) {
-+ this.dimensionType = dimensionType;
-+ this.worldName = worldName;
-+ this.worldDir = worldDir;
-+ this.threadPool = Executors.newFixedThreadPool(Math.max(1, threads), new ThreadFactory() {
-+ private final AtomicInteger threadCounter = new AtomicInteger();
-+
-+ @Override
-+ public Thread newThread(final Runnable run) {
-+ final Thread ret = new Thread(run);
-+
-+ ret.setName("World upgrader thread for world " + ThreadedWorldUpgrader.this.worldName + " #" + this.threadCounter.getAndIncrement());
-+ ret.setUncaughtExceptionHandler((thread, throwable) -> {
-+ LOGGER.fatal("Error upgrading world", throwable);
-+ });
-+
-+ return ret;
-+ }
-+ });
-+ this.dataFixer = dataFixer;
-+ this.generatorKey = generatorKey;
-+ this.removeCaches = removeCaches;
-+ this.recreateRegionFiles = recreateRegionFiles;
-+ }
-+
-+ public void convert() {
-+ final File worldFolder = LevelStorageSource.getStorageFolder(this.worldDir.toPath(), this.dimensionType).toFile();
-+ final DimensionDataStorage worldPersistentData = new DimensionDataStorage(new File(worldFolder, "data"), this.dataFixer);
-+
-+ final File regionFolder = new File(worldFolder, "region");
-+
-+ LOGGER.info("Force upgrading " + this.worldName);
-+ LOGGER.info("Counting regionfiles for " + this.worldName);
-+ final File[] regionFiles = regionFolder.listFiles((final File dir, final String name) -> {
-+ return WorldUpgrader.REGEX.matcher(name).matches();
-+ });
-+ if (regionFiles == null) {
-+ LOGGER.info("Found no regionfiles to convert for world " + this.worldName);
-+ return;
-+ }
-+ LOGGER.info("Found " + regionFiles.length + " regionfiles to convert");
-+ LOGGER.info("Starting conversion now for world " + this.worldName);
-+
-+ final WorldInfo info = new WorldInfo(() -> worldPersistentData,
-+ new ChunkStorage(regionFolder.toPath(), this.dataFixer, false), this.removeCaches, this.dimensionType, this.generatorKey);
-+
-+ long expectedChunks = (long)regionFiles.length * (32L * 32L);
-+
-+ for (final File regionFile : regionFiles) {
-+ final ChunkPos regionPos = RegionFileStorage.getRegionFileCoordinates(regionFile.toPath());
-+ if (regionPos == null) {
-+ expectedChunks -= (32L * 32L);
-+ continue;
-+ }
-+
-+ this.threadPool.execute(new ConvertTask(info, regionPos.x >> 5, regionPos.z >> 5));
-+ }
-+ this.threadPool.shutdown();
-+
-+ final DecimalFormat format = new DecimalFormat("#0.00");
-+
-+ final long start = System.nanoTime();
-+
-+ while (!this.threadPool.isTerminated()) {
-+ final long current = info.convertedChunks.get();
-+
-+ LOGGER.info("{}% completed ({} / {} chunks)...", format.format((double)current / (double)expectedChunks * 100.0), current, expectedChunks);
-+
-+ try {
-+ Thread.sleep(1000L);
-+ } catch (final InterruptedException ignore) {}
-+ }
-+
-+ final long end = System.nanoTime();
-+
-+ try {
-+ info.loader.close();
-+ } catch (final IOException ex) {
-+ LOGGER.fatal("Failed to close chunk loader", ex);
-+ }
-+ LOGGER.info("Completed conversion. Took {}s, {} out of {} chunks needed to be converted/modified ({}%)",
-+ (int)Math.ceil((end - start) * 1.0e-9), info.modifiedChunks.get(), expectedChunks, format.format((double)info.modifiedChunks.get() / (double)expectedChunks * 100.0));
-+ }
-+
-+ private static final class WorldInfo {
-+
-+ public final Supplier<DimensionDataStorage> persistentDataSupplier;
-+ public final ChunkStorage loader;
-+ public final boolean removeCaches;
-+ public final ResourceKey<LevelStem> worldKey;
-+ public final Optional<ResourceKey<Codec<? extends ChunkGenerator>>> generatorKey;
-+ public final AtomicLong convertedChunks = new AtomicLong();
-+ public final AtomicLong modifiedChunks = new AtomicLong();
-+
-+ private WorldInfo(final Supplier<DimensionDataStorage> persistentDataSupplier, final ChunkStorage loader, final boolean removeCaches,
-+ final ResourceKey<LevelStem> worldKey, Optional<ResourceKey<Codec<? extends ChunkGenerator>>> generatorKey) {
-+ this.persistentDataSupplier = persistentDataSupplier;
-+ this.loader = loader;
-+ this.removeCaches = removeCaches;
-+ this.worldKey = worldKey;
-+ this.generatorKey = generatorKey;
-+ }
-+ }
-+
-+ private static final class ConvertTask implements Runnable {
-+
-+ private final WorldInfo worldInfo;
-+ private final int regionX;
-+ private final int regionZ;
-+
-+ public ConvertTask(final WorldInfo worldInfo, final int regionX, final int regionZ) {
-+ this.worldInfo = worldInfo;
-+ this.regionX = regionX;
-+ this.regionZ = regionZ;
-+ }
-+
-+ @Override
-+ public void run() {
-+ final int regionCX = this.regionX << 5;
-+ final int regionCZ = this.regionZ << 5;
-+
-+ final Supplier<DimensionDataStorage> persistentDataSupplier = this.worldInfo.persistentDataSupplier;
-+ final ChunkStorage loader = this.worldInfo.loader;
-+ final boolean removeCaches = this.worldInfo.removeCaches;
-+ final ResourceKey<LevelStem> worldKey = this.worldInfo.worldKey;
-+
-+ for (int cz = regionCZ; cz < (regionCZ + 32); ++cz) {
-+ for (int cx = regionCX; cx < (regionCX + 32); ++cx) {
-+ final ChunkPos chunkPos = new ChunkPos(cx, cz);
-+ try {
-+ // no need to check the coordinate of the chunk, the regionfilecache does that for us
-+
-+ CompoundTag chunkNBT = (loader.read(chunkPos).join()).orElse(null);
-+
-+ if (chunkNBT == null) {
-+ continue;
-+ }
-+
-+ final int versionBefore = ChunkStorage.getVersion(chunkNBT);
-+
-+ chunkNBT = loader.upgradeChunkTag(worldKey, persistentDataSupplier, chunkNBT, this.worldInfo.generatorKey, chunkPos, null);
-+
-+ boolean modified = versionBefore < SharedConstants.getCurrentVersion().getDataVersion().getVersion();
-+
-+ if (removeCaches) {
-+ final CompoundTag level = chunkNBT.getCompound("Level");
-+ modified |= level.contains("Heightmaps");
-+ level.remove("Heightmaps");
-+ modified |= level.contains("isLightOn");
-+ level.remove("isLightOn");
-+ }
-+
-+ if (modified) {
-+ this.worldInfo.modifiedChunks.getAndIncrement();
-+ loader.write(chunkPos, chunkNBT);
-+ }
-+ } catch (final Exception ex) {
-+ LOGGER.error("Error upgrading chunk {}", chunkPos, ex);
-+ } finally {
-+ this.worldInfo.convertedChunks.getAndIncrement();
-+ }
-+ }
-+ }
-+ }
-+ }
-+}
-diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java
-index 244a19ecd0234fa1d7a6ecfea20751595688605d..8893aa1fe4b033fdc25ebe6f3a3606af1f9b5ea7 100644
---- a/src/main/java/net/minecraft/server/Main.java
-+++ b/src/main/java/net/minecraft/server/Main.java
-@@ -392,6 +392,15 @@ public class Main {
- return new WorldLoader.InitConfig(worldloader_d, Commands.CommandSelection.DEDICATED, serverPropertiesHandler.functionPermissionLevel);
- }
-
-+ // Paper start - fix and optimise world upgrading
-+ public static void convertWorldButItWorks(net.minecraft.resources.ResourceKey<net.minecraft.world.level.dimension.LevelStem> dimensionType, net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess worldSession,
-+ DataFixer dataFixer, Optional<net.minecraft.resources.ResourceKey<com.mojang.serialization.Codec<? extends net.minecraft.world.level.chunk.ChunkGenerator>>> generatorKey, boolean removeCaches, boolean recreateRegionFiles) {
-+ int threads = Runtime.getRuntime().availableProcessors() * 3 / 8;
-+ final io.papermc.paper.world.ThreadedWorldUpgrader worldUpgrader = new io.papermc.paper.world.ThreadedWorldUpgrader(dimensionType, worldSession.getLevelId(), worldSession.levelDirectory.path().toFile(), threads, dataFixer, generatorKey, removeCaches, recreateRegionFiles);
-+ worldUpgrader.convert();
-+ }
-+ // Paper end - fix and optimise world upgrading
-+
- public static void forceUpgrade(LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, boolean eraseCache, BooleanSupplier continueCheck, RegistryAccess dynamicRegistryManager, boolean recreateRegionFiles) {
- Main.LOGGER.info("Forcing world upgrade! {}", session.getLevelId()); // CraftBukkit
- WorldUpgrader worldupgrader = new WorldUpgrader(session, dataFixer, dynamicRegistryManager, eraseCache, recreateRegionFiles);
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index 0f3601f2f1a7ac53425129df6498ed0df302dec8..3cb64c6c870454ee3090d6c88bede8b58d9d3361 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -595,11 +595,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- worlddata = new PrimaryLevelData(worldsettings, worldoptions, worlddimensions_b.specialWorldProperty(), lifecycle);
- }
- worlddata.checkName(name); // CraftBukkit - Migration did not rewrite the level.dat; This forces 1.8 to take the last loaded world as respawn (in this case the end)
-- if (this.options.has("forceUpgrade")) {
-- net.minecraft.server.Main.forceUpgrade(worldSession, DataFixers.getDataFixer(), this.options.has("eraseCache"), () -> {
-- return true;
-- }, iregistrycustom_dimension, this.options.has("recreateRegionFiles"));
-- }
-+ // Paper - fix and optimise world upgrading; move down
-
- PrimaryLevelData iworlddataserver = worlddata;
- boolean flag = worlddata.isDebugWorld();
-@@ -614,6 +610,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- biomeProvider = gen.getDefaultBiomeProvider(worldInfo);
- }
-
-+ // Paper start - fix and optimise world upgrading
-+ if (options.has("forceUpgrade")) {
-+ net.minecraft.server.Main.convertWorldButItWorks(
-+ dimensionKey, worldSession, DataFixers.getDataFixer(), worlddimension.generator().getTypeNameForDataFixer(), options.has("eraseCache"), console.has("recreateRegionFiles")
-+ );
-+ }
-+ // Paper end - fix and optimise world upgrading
- ResourceKey<Level> worldKey = ResourceKey.create(Registries.DIMENSION, dimensionKey.location());
-
- if (dimensionKey == LevelStem.OVERWORLD) {
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index a137b4a3be01a0333e5fdc1585098fafeeb4f725..ab502b4384d977ac78b05eaa7dd14eaf811f57b5 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -178,6 +178,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- public final Map<Explosion.CacheKey, Float> explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions
- public java.util.ArrayDeque<net.minecraft.world.level.block.RedstoneTorchBlock.Toggle> redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here
-
-+ // Paper start - fix and optimise world upgrading
-+ // copied from below
-+ public static ResourceKey<DimensionType> getDimensionKey(DimensionType manager) {
-+ return ((org.bukkit.craftbukkit.CraftServer)org.bukkit.Bukkit.getServer()).getHandle().getServer().registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.DIMENSION_TYPE).getResourceKey(manager).orElseThrow(() -> {
-+ return new IllegalStateException("Unregistered dimension type: " + manager);
-+ });
-+ }
-+ // Paper end - fix and optimise world upgrading
-+
- public CraftWorld getWorld() {
- return this.world;
- }
-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 a73a37320da2c141fc2db9d1d61233a34ce0c906..9607e38e39daf8196f1b728c0019a283d730b885 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
-@@ -62,6 +62,29 @@ public class RegionFileStorage implements AutoCloseable {
- }
-
- // Paper start
-+ @Nullable
-+ public static ChunkPos getRegionFileCoordinates(Path file) {
-+ String fileName = file.getFileName().toString();
-+ if (!fileName.startsWith("r.") || !fileName.endsWith(".mca")) {
-+ return null;
-+ }
-+
-+ String[] split = fileName.split("\\.");
-+
-+ if (split.length != 4) {
-+ return null;
-+ }
-+
-+ try {
-+ int x = Integer.parseInt(split[1]);
-+ int z = Integer.parseInt(split[2]);
-+
-+ return new ChunkPos(x << 5, z << 5);
-+ } catch (NumberFormatException ex) {
-+ return null;
-+ }
-+ }
-+
- public synchronized RegionFile getRegionFileIfLoaded(ChunkPos chunkcoordintpair) {
- return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()));
- }
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-index f6b7ae6c00150d2bc33c42a1f8432478d979d38b..554096a467ed9e1c8ab393fdaa8b9c31e1f7bbd4 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-@@ -1365,9 +1365,7 @@ public final class CraftServer implements Server {
- worlddata.checkName(name);
- worlddata.setModdedInfo(this.console.getServerModName(), this.console.getModdedStatus().shouldReportAsModified());
-
-- if (this.console.options.has("forceUpgrade")) {
-- net.minecraft.server.Main.forceUpgrade(worldSession, DataFixers.getDataFixer(), this.console.options.has("eraseCache"), () -> true, iregistrycustom_dimension, this.console.options.has("recreateRegionFiles"));
-- }
-+ // Paper - fix and optimise world upgrading; move down
-
- long j = BiomeManager.obfuscateSeed(worlddata.worldGenOptions().seed()); // Paper - use world seed
- List<CustomSpawner> list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata));
-@@ -1378,6 +1376,13 @@ public final class CraftServer implements Server {
- biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
- }
-
-+ // Paper start - fix and optimise world upgrading
-+ if (this.console.options.has("forceUpgrade")) {
-+ net.minecraft.server.Main.convertWorldButItWorks(
-+ actualDimension, worldSession, DataFixers.getDataFixer(), worlddimension.generator().getTypeNameForDataFixer(), this.console.options.has("eraseCache"), this.console.options.has("recreateRegionFiles")
-+ );
-+ }
-+ // Paper end - fix and optimise world upgrading
- ResourceKey<net.minecraft.world.level.Level> worldKey;
- String levelName = this.getServer().getProperties().levelName;
- if (name.equals(levelName + "_nether")) {
diff --git a/patches/unapplied/server/1068-API-for-checking-sent-chunks.patch b/patches/unapplied/server/1068-API-for-checking-sent-chunks.patch
deleted file mode 100644
index b418da022b..0000000000
--- a/patches/unapplied/server/1068-API-for-checking-sent-chunks.patch
+++ /dev/null
@@ -1,62 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Flo0 <[email protected]>
-Date: Mon, 8 Apr 2024 16:43:16 +0200
-Subject: [PATCH] API for checking sent chunks
-
-
-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 1b090f1e79b996e52097afc49c1cec85936653e6..bf3c5efa0d58c58a5b0b6b73880aaf03c8a37c12 100644
---- a/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java
-+++ b/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java
-@@ -1107,6 +1107,11 @@ public class RegionizedPlayerChunkLoader {
-
- // now all tickets should be removed, which is all of our external state
- }
-+
-+ // For external checks
-+ public it.unimi.dsi.fastutil.longs.LongOpenHashSet getSentChunksRaw() {
-+ return this.sentChunks;
-+ }
- }
-
- // TODO rebase into util patch
-diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-index 3fbc0312ed291a3878c26c005bfc79f417c695e4..44f4665db613c558078df5bb49106e4ca5679dfe 100644
---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-@@ -3392,6 +3392,35 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
- }
- // Paper end
-
-+ // Paper start - Add chunk view API
-+ @Override
-+ public Set<java.lang.Long> getSentChunkKeys() {
-+ org.spigotmc.AsyncCatcher.catchOp("accessing sent chunks");
-+ return it.unimi.dsi.fastutil.longs.LongSets.unmodifiable(
-+ this.getHandle().chunkLoader.getSentChunksRaw().clone()
-+ );
-+ }
-+
-+ @Override
-+ public Set<org.bukkit.Chunk> getSentChunks() {
-+ org.spigotmc.AsyncCatcher.catchOp("accessing sent chunks");
-+ final it.unimi.dsi.fastutil.longs.LongOpenHashSet rawChunkKeys = this.getHandle().chunkLoader.getSentChunksRaw();
-+ final it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<org.bukkit.Chunk> chunks = new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(rawChunkKeys.size());
-+ final org.bukkit.World world = this.getWorld();
-+
-+ final it.unimi.dsi.fastutil.longs.LongIterator iter = this.getHandle().chunkLoader.getSentChunksRaw().longIterator();
-+ while (iter.hasNext()) chunks.add(world.getChunkAt(iter.nextLong(), false));
-+
-+ return it.unimi.dsi.fastutil.objects.ObjectSets.unmodifiable(chunks);
-+ }
-+
-+ @Override
-+ public boolean isChunkSent(final long chunkKey) {
-+ org.spigotmc.AsyncCatcher.catchOp("accessing sent chunks");
-+ return this.getHandle().chunkLoader.getSentChunksRaw().contains(chunkKey);
-+ }
-+ // Paper end
-+
- public Player.Spigot spigot()
- {
- return this.spigot;