diff options
author | Owen1212055 <[email protected]> | 2024-04-25 08:36:48 -0400 |
---|---|---|
committer | Owen1212055 <[email protected]> | 2024-04-25 08:36:48 -0400 |
commit | 345b5c1c4c3f99756029f039b8e063ed6f8c3181 (patch) | |
tree | c229a0fd351d859a98e60ccce197b358a8a9fb62 /patches/unapplied | |
parent | 6da0d8cc91be3b8e1c84c0436f49654cdde6de2f (diff) | |
download | Paper-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.patch | 385 | ||||
-rw-r--r-- | patches/unapplied/server/1068-API-for-checking-sent-chunks.patch | 62 |
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; |