aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/removed/1.14/0361-Async-Chunk-Loading-and-Generation.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patches/removed/1.14/0361-Async-Chunk-Loading-and-Generation.patch')
-rw-r--r--patches/removed/1.14/0361-Async-Chunk-Loading-and-Generation.patch2503
1 files changed, 0 insertions, 2503 deletions
diff --git a/patches/removed/1.14/0361-Async-Chunk-Loading-and-Generation.patch b/patches/removed/1.14/0361-Async-Chunk-Loading-and-Generation.patch
deleted file mode 100644
index 494a0dd328..0000000000
--- a/patches/removed/1.14/0361-Async-Chunk-Loading-and-Generation.patch
+++ /dev/null
@@ -1,2503 +0,0 @@
-From 3e8f931198ca4a9877415012df19ec4efc8c54b1 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sat, 21 Jul 2018 16:55:04 -0400
-Subject: [PATCH] Async Chunk Loading and Generation
-
-This brings back parity to 1.12 and older versions in that any
-chunk requested as part of the PlayerChunkMap can be loaded
-asynchronously, since the chunk isn't needed "immediately".
-
-The previous system used by CraftBukkit has been completely abandoned, as
-mojang has put more concurrency checks into the process.
-
-The new process is no longer lock free, but tries to maintain locks as
-short as possible.
-
-But with 1.13, we now have Chunk Conversions too. A main issue about
-keeping just loading parity to 1.12 is that standard loads now
-are treated as generation level events, to run the converter on
-another thread.
-
-However mojangs code was pretty bad here and doesn't actually provide
-any concurrency...
-
-Mojangs code is still not thread safe, and can only operate on
-one world per thread safely, but this is still a major improvement
-to get world generation off of the main thread for exploration.
-
-This change brings Chunk Requests triggered by the Chunk Map to be
-lazily loaded asynchronously.
-
-Standard chunk loads can load in parallel across a shared executor.
-
-However, chunk conversions and generations must only run one per world
-at a time, so we have a single thread executor for those operations
-per world, that all of those requests get scheduled to.
-
-getChunkAt method is now thread safe, but has not been tested in
-use by other threads for generations, but should be safe to do.
-
-However, we are not encouraging plugins to go getting chunks async,
-as while looking the chunk up may be safe, absolutely nothing about
-reading or writing to the chunk will be safe, so plugins still
-should not be touching chunks asynchronously!
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-index 07d7976d21..c25db284ff 100644
---- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-@@ -376,4 +376,57 @@ public class PaperConfig {
- }
- }
- }
-+
-+ public static boolean asyncChunks = false;
-+ public static boolean asyncChunkGeneration = true;
-+ public static boolean asyncChunkGenThreadPerWorld = true;
-+ public static int asyncChunkLoadThreads = -1;
-+ private static void asyncChunks() {
-+ if (version < 15) {
-+ boolean enabled = config.getBoolean("settings.async-chunks", true);
-+ ConfigurationSection section = config.createSection("settings.async-chunks");
-+ section.set("enable", enabled);
-+ section.set("load-threads", -1);
-+ section.set("generation", true);
-+ section.set("thread-per-world-generation", true);
-+ }
-+
-+ asyncChunks = getBoolean("settings.async-chunks.enable", true);
-+ asyncChunkGeneration = getBoolean("settings.async-chunks.generation", true);
-+ asyncChunkGenThreadPerWorld = getBoolean("settings.async-chunks.thread-per-world-generation", true);
-+ asyncChunkLoadThreads = getInt("settings.async-chunks.load-threads", -1);
-+ if (asyncChunkLoadThreads <= 0) {
-+ asyncChunkLoadThreads = (int) Math.min(Integer.getInteger("paper.maxChunkThreads", 8), Runtime.getRuntime().availableProcessors() * 1.5);
-+ }
-+
-+ // Let Shared Host set some limits
-+ String sharedHostEnvGen = System.getenv("PAPER_ASYNC_CHUNKS_SHARED_HOST_GEN");
-+ String sharedHostEnvLoad = System.getenv("PAPER_ASYNC_CHUNKS_SHARED_HOST_LOAD");
-+ if ("1".equals(sharedHostEnvGen)) {
-+ log("Async Chunks - Generation: Your host has requested to use a single thread world generation");
-+ asyncChunkGenThreadPerWorld = false;
-+ } else if ("2".equals(sharedHostEnvGen)) {
-+ log("Async Chunks - Generation: Your host has disabled async world generation - You will experience lag from world generation");
-+ asyncChunkGeneration = false;
-+ }
-+
-+ if (sharedHostEnvLoad != null) {
-+ try {
-+ asyncChunkLoadThreads = Math.max(1, Math.min(asyncChunkLoadThreads, Integer.parseInt(sharedHostEnvLoad)));
-+ } catch (NumberFormatException ignored) {}
-+ }
-+
-+ if (!asyncChunks) {
-+ log("Async Chunks: Disabled - Chunks will be managed synchronosuly, and will cause tremendous lag.");
-+ } else {
-+ log("Async Chunks: Enabled - Chunks will be loaded much faster, without lag.");
-+ if (!asyncChunkGeneration) {
-+ log("Async Chunks - Generation: Disabled - Chunks will be generated synchronosuly, and will cause tremendous lag.");
-+ } else if (asyncChunkGenThreadPerWorld) {
-+ log("Async Chunks - Generation: Enabled - Chunks will be generated much faster, without lag.");
-+ } else {
-+ log("Async Chunks - Generation: Enabled (Single Thread) - Chunks will be generated much faster, without lag.");
-+ }
-+ }
-+ }
- }
-diff --git a/src/main/java/com/destroystokyo/paper/util/PriorityQueuedExecutor.java b/src/main/java/com/destroystokyo/paper/util/PriorityQueuedExecutor.java
-new file mode 100644
-index 0000000000..8f18c28695
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/util/PriorityQueuedExecutor.java
-@@ -0,0 +1,347 @@
-+package com.destroystokyo.paper.util;
-+
-+import javax.annotation.Nonnull;
-+import java.util.ArrayList;
-+import java.util.List;
-+import java.util.concurrent.AbstractExecutorService;
-+import java.util.concurrent.CompletableFuture;
-+import java.util.concurrent.ConcurrentLinkedQueue;
-+import java.util.concurrent.RejectedExecutionException;
-+import java.util.concurrent.TimeUnit;
-+import java.util.concurrent.atomic.AtomicBoolean;
-+import java.util.concurrent.atomic.AtomicInteger;
-+import java.util.function.Supplier;
-+
-+/**
-+ * Implements an Executor Service that allows specifying Task Priority
-+ * and bumping of task priority.
-+ *
-+ * This is a non blocking executor with 3 priority levels.
-+ *
-+ * URGENT: Rarely used, something that is critical to take action now.
-+ * HIGH: Something with more importance than the base tasks
-+ *
-+ * @author Daniel Ennis &lt;[email protected]&gt;
-+ */
-+@SuppressWarnings({"WeakerAccess", "UnusedReturnValue", "unused"})
-+public class PriorityQueuedExecutor extends AbstractExecutorService {
-+
-+ private final ConcurrentLinkedQueue<Runnable> urgent = new ConcurrentLinkedQueue<>();
-+ private final ConcurrentLinkedQueue<Runnable> high = new ConcurrentLinkedQueue<>();
-+ private final ConcurrentLinkedQueue<Runnable> normal = new ConcurrentLinkedQueue<>();
-+ private final List<Thread> threads = new ArrayList<>();
-+ private final RejectionHandler handler;
-+
-+ private volatile boolean shuttingDown = false;
-+ private volatile boolean shuttingDownNow = false;
-+
-+ public PriorityQueuedExecutor(String name) {
-+ this(name, Math.max(1, Runtime.getRuntime().availableProcessors() - 1));
-+ }
-+
-+ public PriorityQueuedExecutor(String name, int threads) {
-+ this(name, threads, Thread.NORM_PRIORITY, null);
-+ }
-+
-+ public PriorityQueuedExecutor(String name, int threads, int threadPriority) {
-+ this(name, threads, threadPriority, null);
-+ }
-+
-+ public PriorityQueuedExecutor(String name, int threads, RejectionHandler handler) {
-+ this(name, threads, Thread.NORM_PRIORITY, handler);
-+ }
-+
-+ public PriorityQueuedExecutor(String name, int threads, int threadPriority, RejectionHandler handler) {
-+ for (int i = 0; i < threads; i++) {
-+ ExecutorThread thread = new ExecutorThread(this::processQueues);
-+ thread.setDaemon(true);
-+ thread.setName(threads == 1 ? name : name + "-" + (i + 1));
-+ thread.setPriority(threadPriority);
-+ thread.start();
-+ this.threads.add(thread);
-+ }
-+ if (handler == null) {
-+ handler = ABORT_POLICY;
-+ }
-+ this.handler = handler;
-+ }
-+
-+ /**
-+ * If the Current thread belongs to a PriorityQueuedExecutor, return that Executro
-+ * @return The executor that controls this thread
-+ */
-+ public static PriorityQueuedExecutor getExecutor() {
-+ if (!(Thread.currentThread() instanceof ExecutorThread)) {
-+ return null;
-+ }
-+ return ((ExecutorThread) Thread.currentThread()).getExecutor();
-+ }
-+
-+ public void shutdown() {
-+ shuttingDown = true;
-+ synchronized (this) {
-+ this.notifyAll();
-+ }
-+ }
-+
-+ @Nonnull
-+ @Override
-+ public List<Runnable> shutdownNow() {
-+ shuttingDown = true;
-+ shuttingDownNow = true;
-+ List<Runnable> tasks = new ArrayList<>(high.size() + normal.size());
-+ Runnable run;
-+ while ((run = getTask()) != null) {
-+ tasks.add(run);
-+ }
-+
-+ return tasks;
-+ }
-+
-+ @Override
-+ public boolean isShutdown() {
-+ return shuttingDown;
-+ }
-+
-+ @Override
-+ public boolean isTerminated() {
-+ if (!shuttingDown) {
-+ return false;
-+ }
-+ return high.isEmpty() && normal.isEmpty();
-+ }
-+
-+ @Override
-+ public boolean awaitTermination(long timeout, @Nonnull TimeUnit unit) {
-+ synchronized (this) {
-+ this.notifyAll();
-+ }
-+ final long wait = unit.toNanos(timeout);
-+ final long max = System.nanoTime() + wait;
-+ for (;!threads.isEmpty() && System.nanoTime() < max;) {
-+ threads.removeIf(thread -> !thread.isAlive());
-+ }
-+ return isTerminated();
-+ }
-+
-+
-+ public PendingTask<Void> createPendingTask(Runnable task) {
-+ return createPendingTask(task, Priority.NORMAL);
-+ }
-+ public PendingTask<Void> createPendingTask(Runnable task, Priority priority) {
-+ return createPendingTask(() -> {
-+ task.run();
-+ return null;
-+ }, priority);
-+ }
-+
-+ public <T> PendingTask<T> createPendingTask(Supplier<T> task) {
-+ return createPendingTask(task, Priority.NORMAL);
-+ }
-+
-+ public <T> PendingTask<T> createPendingTask(Supplier<T> task, Priority priority) {
-+ return new PendingTask<>(task, priority);
-+ }
-+
-+ public PendingTask<Void> submitTask(Runnable run) {
-+ return createPendingTask(run).submit();
-+ }
-+
-+ public PendingTask<Void> submitTask(Runnable run, Priority priority) {
-+ return createPendingTask(run, priority).submit();
-+ }
-+
-+ public <T> PendingTask<T> submitTask(Supplier<T> run) {
-+ return createPendingTask(run).submit();
-+ }
-+
-+ public <T> PendingTask<T> submitTask(Supplier<T> run, Priority priority) {
-+ PendingTask<T> task = createPendingTask(run, priority);
-+ return task.submit();
-+ }
-+
-+ @Override
-+ public void execute(@Nonnull Runnable command) {
-+ submitTask(command);
-+ }
-+
-+ public boolean isCurrentThread() {
-+ final Thread thread = Thread.currentThread();
-+ if (!(thread instanceof ExecutorThread)) {
-+ return false;
-+ }
-+ return ((ExecutorThread) thread).getExecutor() == this;
-+ }
-+
-+ public Runnable getUrgentTask() {
-+ return urgent.poll();
-+ }
-+
-+ public Runnable getTask() {
-+ Runnable run = urgent.poll();
-+ if (run != null) {
-+ return run;
-+ }
-+ run = high.poll();
-+ if (run != null) {
-+ return run;
-+ }
-+ return normal.poll();
-+ }
-+
-+ private void processQueues() {
-+ Runnable run = null;
-+ while (true) {
-+ if (run != null) {
-+ run.run();
-+ }
-+ if (shuttingDownNow) {
-+ return;
-+ }
-+ if ((run = getTask()) != null) {
-+ continue;
-+ }
-+ synchronized (PriorityQueuedExecutor.this) {
-+ if ((run = getTask()) != null) {
-+ continue;
-+ }
-+
-+ if (shuttingDown || shuttingDownNow) {
-+ return;
-+ }
-+ try {
-+ PriorityQueuedExecutor.this.wait();
-+ } catch (InterruptedException ignored) {
-+ }
-+ }
-+ }
-+ }
-+
-+ public boolean processUrgentTasks() {
-+ Runnable run;
-+ boolean hadTask = false;
-+ while ((run = getUrgentTask()) != null) {
-+ run.run();
-+ hadTask = true;
-+ }
-+ return hadTask;
-+ }
-+
-+ public enum Priority {
-+ NORMAL, HIGH, URGENT
-+ }
-+
-+ public class ExecutorThread extends Thread {
-+ public ExecutorThread(Runnable runnable) {
-+ super(runnable);
-+ }
-+
-+ public PriorityQueuedExecutor getExecutor() {
-+ return PriorityQueuedExecutor.this;
-+ }
-+ }
-+
-+ public class PendingTask <T> implements Runnable {
-+
-+ private final AtomicBoolean hasRan = new AtomicBoolean();
-+ private final AtomicInteger submitted = new AtomicInteger(-1);
-+ private final AtomicInteger priority;
-+ private final Supplier<T> run;
-+ private final CompletableFuture<T> future = new CompletableFuture<>();
-+ private volatile PriorityQueuedExecutor executor;
-+
-+ public PendingTask(Supplier<T> run) {
-+ this(run, Priority.NORMAL);
-+ }
-+
-+ public PendingTask(Supplier<T> run, Priority priority) {
-+ this.priority = new AtomicInteger(priority.ordinal());
-+ this.run = run;
-+ }
-+
-+ public boolean cancel() {
-+ return hasRan.compareAndSet(false, true);
-+ }
-+
-+ @Override
-+ public void run() {
-+ if (!hasRan.compareAndSet(false, true)) {
-+ return;
-+ }
-+
-+ try {
-+ future.complete(run.get());
-+ } catch (Throwable e) {
-+ future.completeExceptionally(e);
-+ }
-+ }
-+
-+ public void bumpPriority() {
-+ bumpPriority(Priority.HIGH);
-+ }
-+
-+ public void bumpPriority(Priority newPriority) {
-+ for (;;) {
-+ int current = this.priority.get();
-+ int ordinal = newPriority.ordinal();
-+ if (current >= ordinal || priority.compareAndSet(current, ordinal)) {
-+ break;
-+ }
-+ }
-+
-+
-+ if (this.submitted.get() == -1 || this.hasRan.get()) {
-+ return;
-+ }
-+
-+ // Only resubmit if it hasnt ran yet and has been submitted
-+ submit();
-+ }
-+
-+ public CompletableFuture<T> onDone() {
-+ return future;
-+ }
-+
-+ public PendingTask<T> submit() {
-+ if (shuttingDown) {
-+ handler.onRejection(this, PriorityQueuedExecutor.this);
-+ return this;
-+ }
-+ for (;;) {
-+ final int submitted = this.submitted.get();
-+ final int priority = this.priority.get();
-+ if (submitted == priority) {
-+ return this;
-+ }
-+ if (this.submitted.compareAndSet(submitted, priority)) {
-+ if (priority == Priority.URGENT.ordinal()) {
-+ urgent.add(this);
-+ } else if (priority == Priority.HIGH.ordinal()) {
-+ high.add(this);
-+ } else {
-+ normal.add(this);
-+ }
-+
-+ break;
-+ }
-+ }
-+
-+ synchronized (PriorityQueuedExecutor.this) {
-+ // Wake up a thread to take this work
-+ PriorityQueuedExecutor.this.notify();
-+ }
-+ return this;
-+ }
-+ }
-+ public interface RejectionHandler {
-+ void onRejection(Runnable run, PriorityQueuedExecutor executor);
-+ }
-+
-+ public static final RejectionHandler ABORT_POLICY = (run, executor) -> {
-+ throw new RejectedExecutionException("Executor has been shutdown");
-+ };
-+ public static final RejectionHandler CALLER_RUNS_POLICY = (run, executor) -> {
-+ run.run();
-+ };
-+
-+}
-diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
-index 4ae0acbf13..4f64072a7b 100644
---- a/src/main/java/net/minecraft/server/Chunk.java
-+++ b/src/main/java/net/minecraft/server/Chunk.java
-@@ -190,6 +190,7 @@ public class Chunk implements IChunkAccess {
-
- for (k = 0; k < this.sections.length; ++k) {
- this.sections[k] = protochunk.getSections()[k];
-+ if (this.sections[k] != null) this.sections[k].disableLocks(); // Paper - Async Chunks - disable locks used during world gen
- }
-
- Iterator iterator = protochunk.s().iterator();
-diff --git a/src/main/java/net/minecraft/server/ChunkMap.java b/src/main/java/net/minecraft/server/ChunkMap.java
-index 2021c0d02e..154ab09e0c 100644
---- a/src/main/java/net/minecraft/server/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/ChunkMap.java
-@@ -14,9 +14,17 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
- }
-
- public Chunk put(long i, Chunk chunk) {
-+ org.spigotmc.AsyncCatcher.catchOp("Async Chunk put"); // Paper
- chunk.world.timings.syncChunkLoadPostTimer.startTiming(); // Paper
- lastChunkByPos = chunk; // Paper
-- Chunk chunk1 = (Chunk) super.put(i, chunk);
-+ // Paper start
-+ Chunk chunk1;
-+ synchronized (this) {
-+ // synchronize so any async gets are safe
-+ chunk1 = (Chunk) super.put(i, chunk);
-+ }
-+ if (chunk1 == null) { // Paper - we should never be overwriting chunks
-+ // Paper end
- ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i);
-
- for (int j = chunkcoordintpair.x - 1; j <= chunkcoordintpair.x + 1; ++j) {
-@@ -47,7 +55,11 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
- chunk.setNeighborLoaded(x, z);
- }
- }
-+ // Paper start
-+ } } else {
-+ a.error("Overwrote existing chunk! (" + chunk.world.getWorld().getName() + ":" + chunk.locX+"," + chunk.locZ + ")", new IllegalStateException());
- }
-+ // Paper end
- // Paper start - if this is a spare chunk (not part of any players view distance), go ahead and queue it for unload.
- if (!((WorldServer)chunk.world).getPlayerChunkMap().isChunkInUse(chunk.locX, chunk.locZ)) {
- if (chunk.world.paperConfig.delayChunkUnloadsBy > 0) {
-@@ -64,11 +76,19 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
- }
-
- public Chunk put(Long olong, Chunk chunk) {
-- return this.put(olong, chunk);
-+ return MCUtil.ensureMain("Chunk Put", () -> this.put(olong.longValue(), chunk)); // Paper
- }
-
- public Chunk remove(long i) {
-- Chunk chunk = (Chunk) super.remove(i);
-+ // Paper start
-+ org.spigotmc.AsyncCatcher.catchOp("Async Chunk remove");
-+ Chunk chunk;
-+ synchronized (this) {
-+ // synchronize so any async gets are safe
-+ chunk = super.remove(i);
-+ }
-+ if (chunk != null) { // Paper - don't decrement if we didn't remove anything
-+ // Paper end
- ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i);
-
- for (int j = chunkcoordintpair.x - 1; j <= chunkcoordintpair.x + 1; ++j) {
-@@ -84,6 +104,7 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
- }
-
- // Paper start
-+ } // close if (chunk != null)
- if (lastChunkByPos != null && i == lastChunkByPos.chunkKey) {
- lastChunkByPos = null;
- }
-@@ -93,15 +114,22 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
-
- @Override
- public Chunk get(long l) {
-- if (lastChunkByPos != null && l == lastChunkByPos.chunkKey) {
-- return lastChunkByPos;
-+ if (MCUtil.isMainThread()) {
-+ if (lastChunkByPos != null && l == lastChunkByPos.chunkKey) {
-+ return lastChunkByPos;
-+ }
-+ final Chunk chunk = super.get(l);
-+ return chunk != null ? (lastChunkByPos = chunk) : null;
-+ } else {
-+ synchronized (this) {
-+ return super.get(l);
-+ }
- }
-- return lastChunkByPos = super.get(l);
- }
- // Paper end
-
- public Chunk remove(Object object) {
-- return this.remove((Long) object);
-+ return MCUtil.ensureMain("Chunk Remove", () -> this.remove(((Long) object).longValue())); // Paper
- }
-
- public void putAll(Map<? extends Long, ? extends Chunk> map) {
-diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-index 186cfda7e4..781e068770 100644
---- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
-+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-@@ -33,12 +33,12 @@ public class ChunkProviderServer implements IChunkProvider {
- private long lastProcessedSaves = 0L; // Paper
- private long lastSaveStatPrinted = System.currentTimeMillis();
- // Paper end
-- public final Long2ObjectMap<Chunk> chunks = Long2ObjectMaps.synchronize(new ChunkMap(8192));
-+ public final Long2ObjectMap<Chunk> chunks = new ChunkMap(8192); // Paper - remove synchronize - we keep everything on main for manip
- private Chunk lastChunk;
- private final ChunkTaskScheduler chunkScheduler;
-- private final SchedulerBatch<ChunkCoordIntPair, ChunkStatus, ProtoChunk> batchScheduler;
-+ final SchedulerBatch<ChunkCoordIntPair, ChunkStatus, ProtoChunk> batchScheduler; // Paper
- public final WorldServer world;
-- private final IAsyncTaskHandler asyncTaskHandler;
-+ final IAsyncTaskHandler asyncTaskHandler; // Paper
-
- public ChunkProviderServer(WorldServer worldserver, IChunkLoader ichunkloader, ChunkGenerator<?> chunkgenerator, IAsyncTaskHandler iasynctaskhandler) {
- this.world = worldserver;
-@@ -75,10 +75,77 @@ public class ChunkProviderServer implements IChunkProvider {
- this.unloadQueue.remove(ChunkCoordIntPair.a(i, j));
- }
-
-+ // Paper start - defaults if Async Chunks is not enabled
-+ boolean chunkGoingToExists(int x, int z) {
-+ final long k = ChunkCoordIntPair.asLong(x, z);
-+ return chunkScheduler.progressCache.containsKey(k);
-+ }
-+ public void bumpPriority(ChunkCoordIntPair coords) {
-+ // do nothing, override in async
-+ }
-+
-+ public List<ChunkCoordIntPair> getSpiralOutChunks(BlockPosition blockposition, int radius) {
-+ List<ChunkCoordIntPair> list = com.google.common.collect.Lists.newArrayList();
-+
-+ list.add(new ChunkCoordIntPair(blockposition.getX() >> 4, blockposition.getZ() >> 4));
-+ for (int r = 1; r <= radius; r++) {
-+ int x = -r;
-+ int z = r;
-+
-+ // Iterates the edge of half of the box; then negates for other half.
-+ while (x <= r && z > -r) {
-+ list.add(new ChunkCoordIntPair((blockposition.getX() + (x << 4)) >> 4, (blockposition.getZ() + (z << 4)) >> 4));
-+ list.add(new ChunkCoordIntPair((blockposition.getX() - (x << 4)) >> 4, (blockposition.getZ() - (z << 4)) >> 4));
-+
-+ if (x < r) {
-+ x++;
-+ } else {
-+ z--;
-+ }
-+ }
-+ }
-+ return list;
-+ }
-+
-+ public Chunk getChunkAt(int x, int z, boolean load, boolean gen, Consumer<Chunk> consumer) {
-+ return getChunkAt(x, z, load, gen, false, consumer);
-+ }
-+ public Chunk getChunkAt(int x, int z, boolean load, boolean gen, boolean priority, Consumer<Chunk> consumer) {
-+ Chunk chunk = getChunkAt(x, z, load, gen);
-+ if (consumer != null) {
-+ consumer.accept(chunk);
-+ }
-+ return chunk;
-+ }
-+
-+ PaperAsyncChunkProvider.CancellableChunkRequest requestChunk(int x, int z, boolean gen, boolean priority, Consumer<Chunk> consumer) {
-+ Chunk chunk = getChunkAt(x, z, gen, priority, consumer);
-+ return new PaperAsyncChunkProvider.CancellableChunkRequest() {
-+ @Override
-+ public void cancel() {
-+
-+ }
-+
-+ @Override
-+ public Chunk getChunk() {
-+ return chunk;
-+ }
-+ };
-+ }
-+ // Paper end
-+
- @Nullable
- public Chunk getChunkAt(int i, int j, boolean flag, boolean flag1) {
- IChunkLoader ichunkloader = this.chunkLoader;
- Chunk chunk;
-+ // Paper start - do already loaded checks before synchronize
-+ long k = ChunkCoordIntPair.a(i, j);
-+ chunk = (Chunk) this.chunks.get(k);
-+ if (chunk != null) {
-+ //this.lastChunk = chunk; // Paper remove vanilla lastChunk
-+ return chunk;
-+ }
-+ // Paper end
-
- synchronized (this.chunkLoader) {
- // Paper start - remove vanilla lastChunk, we do it more accurately
-@@ -86,13 +153,15 @@ public class ChunkProviderServer implements IChunkProvider {
- return this.lastChunk;
- }*/ // Paper end
-
-- long k = ChunkCoordIntPair.a(i, j);
-+ // Paper start - move up
-+ //long k = ChunkCoordIntPair.a(i, j);
-
-- chunk = (Chunk) this.chunks.get(k);
-+ /*chunk = (Chunk) this.chunks.get(k);
- if (chunk != null) {
- //this.lastChunk = chunk; // Paper remove vanilla lastChunk
- return chunk;
-- }
-+ }*/
-+ // Paper end
-
- if (flag) {
- try (co.aikar.timings.Timing timing = world.timings.syncChunkLoadTimer.startTiming()) { // Paper
-@@ -150,7 +219,8 @@ public class ChunkProviderServer implements IChunkProvider {
- return (IChunkAccess) (chunk != null ? chunk : (IChunkAccess) this.chunkScheduler.b(new ChunkCoordIntPair(i, j), flag));
- }
-
-- public CompletableFuture<ProtoChunk> a(Iterable<ChunkCoordIntPair> iterable, Consumer<Chunk> consumer) {
-+ public CompletableFuture<Void> loadAllChunks(Iterable<ChunkCoordIntPair> iterable, Consumer<Chunk> consumer) { return a(iterable, consumer).thenCompose(protoChunk -> null); } // Paper - overriden in async chunk provider
-+ private CompletableFuture<ProtoChunk> a(Iterable<ChunkCoordIntPair> iterable, Consumer<Chunk> consumer) { // Paper - mark private, use above method
- this.batchScheduler.b();
- Iterator iterator = iterable.iterator();
-
-@@ -168,6 +238,7 @@ public class ChunkProviderServer implements IChunkProvider {
- return this.batchScheduler.c();
- }
-
-+ ReportedException generateChunkError(int i, int j, Throwable throwable) { return a(i, j, throwable); } // Paper - OBFHELPER
- private ReportedException a(int i, int j, Throwable throwable) {
- CrashReport crashreport = CrashReport.a(throwable, "Exception generating new chunk");
- CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Chunk to be generated");
-@@ -289,11 +360,13 @@ public class ChunkProviderServer implements IChunkProvider {
- }
-
- public void close() {
-- try {
-+ // Paper start - we do not need to wait for chunk generations to finish on close
-+ /*try {
- this.batchScheduler.a();
- } catch (InterruptedException interruptedexception) {
- ChunkProviderServer.a.error("Couldn't stop taskManager", interruptedexception);
-- }
-+ }*/
-+ // Paper end
-
- }
-
-diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
-index d938eb3749..7734712af9 100644
---- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
-+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
-@@ -119,7 +119,7 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
- // CraftBukkit start
- private boolean check(ChunkProviderServer cps, int x, int z) throws IOException {
- if (cps != null) {
-- com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread");
-+ //com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); // Paper - this is safe
- if (cps.isLoaded(x, z)) {
- return true;
- }
-@@ -204,10 +204,13 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
- }
- }
-
-+ private final Object legacyStructureLock = new Object(); // Paper
-+ public void getPersistentStructureLegacy(DimensionManager dimensionmanager, @Nullable PersistentCollection persistentcollection) { this.a(dimensionmanager, persistentcollection); } // Paper
- public void a(DimensionManager dimensionmanager, @Nullable PersistentCollection persistentcollection) {
- if (this.e == null) {
-+ synchronized (legacyStructureLock){ if (this.e == null) { // Paper
- this.e = PersistentStructureLegacy.a(dimensionmanager, persistentcollection);
-- }
-+ } } } // Paper
-
- }
-
-@@ -385,11 +388,12 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
- }
- };
- } else {
-+ /* // Paper start - we will never invoke this in an unsafe way
- NBTTagCompound nbttagcompound2 = this.a(world, chunkcoordintpair.x, chunkcoordintpair.z);
-
- if (nbttagcompound2 != null && this.a(nbttagcompound2) == ChunkStatus.Type.LEVELCHUNK) {
- return;
-- }
-+ }*/ // Paper end
-
- completion = new Supplier<NBTTagCompound>() {
- public NBTTagCompound get() {
-diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java
-index 2af07ae592..9c6844d441 100644
---- a/src/main/java/net/minecraft/server/ChunkSection.java
-+++ b/src/main/java/net/minecraft/server/ChunkSection.java
-@@ -25,7 +25,17 @@ public class ChunkSection {
- this.skyLight = new NibbleArray();
- }
-
-+ // Paper start - Async Chunks - Lock during world gen
-+ if (chunk instanceof ProtoChunk) {
-+ this.blockIds.enableLocks();
-+ } else {
-+ this.blockIds.disableLocks();
-+ }
-+ }
-+ void disableLocks() {
-+ this.blockIds.disableLocks();
- }
-+ // Paper end
-
- public IBlockData getType(int i, int j, int k) {
- return (IBlockData) this.blockIds.a(i, j, k);
-diff --git a/src/main/java/net/minecraft/server/ChunkTaskScheduler.java b/src/main/java/net/minecraft/server/ChunkTaskScheduler.java
-index d3898599f8..8f061f5ca3 100644
---- a/src/main/java/net/minecraft/server/ChunkTaskScheduler.java
-+++ b/src/main/java/net/minecraft/server/ChunkTaskScheduler.java
-@@ -17,13 +17,14 @@ public class ChunkTaskScheduler extends Scheduler<ChunkCoordIntPair, ChunkStatus
- private final ChunkGenerator<?> d;
- private final IChunkLoader e;
- private final IAsyncTaskHandler f;
-- private final Long2ObjectMap<Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a> progressCache = new ExpiringMap<Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a>(8192, 5000) {
-+ protected final Long2ObjectMap<Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a> progressCache = new ExpiringMap<Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a>(8192, 5000) { // Paper - protected
- protected boolean a(Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a scheduler_a) {
- ProtoChunk protochunk = (ProtoChunk) scheduler_a.a();
-
- return !protochunk.ab_() /*&& !protochunk.h()*/; // Paper
- }
- };
-+ private final Long2ObjectMap<java.util.concurrent.CompletableFuture<Scheduler.a>> pendingSchedulers = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); // Paper
-
- public ChunkTaskScheduler(int i, World world, ChunkGenerator<?> chunkgenerator, IChunkLoader ichunkloader, IAsyncTaskHandler iasynctaskhandler) {
- super("WorldGen", i, ChunkStatus.FINALIZED, () -> {
-@@ -47,8 +48,28 @@ public class ChunkTaskScheduler extends Scheduler<ChunkCoordIntPair, ChunkStatus
- protected Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a a(ChunkCoordIntPair chunkcoordintpair, boolean flag) {
- IChunkLoader ichunkloader = this.e;
-
-- synchronized (this.e) {
-- return flag ? (Scheduler.a) this.progressCache.computeIfAbsent(chunkcoordintpair.a(), (i) -> {
-+ // Paper start - refactor a lot of this - avoid generating a chunk while holding lock on expiring map
-+ java.util.concurrent.CompletableFuture<Scheduler.a> pending = null;
-+ boolean created = false;
-+ long key = chunkcoordintpair.a();
-+ synchronized (pendingSchedulers) {
-+ Scheduler.a existing = this.progressCache.get(key);
-+ if (existing != null) {
-+ return existing;
-+ }
-+ pending = this.pendingSchedulers.get(key);
-+ if (pending == null) {
-+ if (!flag) {
-+ return null;
-+ }
-+ created = true;
-+ pending = new java.util.concurrent.CompletableFuture<>();
-+ pendingSchedulers.put(key, pending);
-+ }
-+ }
-+ if (created) {
-+ java.util.function.Function<Long, Scheduler.a> get = (i) -> {
-+ // Paper end
- ProtoChunk protochunk;
-
- try {
-@@ -67,8 +88,18 @@ public class ChunkTaskScheduler extends Scheduler<ChunkCoordIntPair, ChunkStatus
- } else {
- return new Scheduler.a(chunkcoordintpair, new ProtoChunk(chunkcoordintpair, ChunkConverter.a, this.getWorld()), ChunkStatus.EMPTY); // Paper - Anti-Xray
- }
-- }) : (Scheduler.a) this.progressCache.get(chunkcoordintpair.a());
-+ // Paper start
-+ };
-+ Scheduler.a scheduler = get.apply(key);
-+ progressCache.put(key, scheduler);
-+ pending.complete(scheduler);
-+ synchronized (pendingSchedulers) {
-+ pendingSchedulers.remove(key);
-+ }
-+ return scheduler;
- }
-+ return pending.join();
-+ // Paper end
- }
-
- protected ProtoChunk a(ChunkCoordIntPair chunkcoordintpair, ChunkStatus chunkstatus, Map<ChunkCoordIntPair, ProtoChunk> map) {
-diff --git a/src/main/java/net/minecraft/server/DataPaletteBlock.java b/src/main/java/net/minecraft/server/DataPaletteBlock.java
-index 454903a0e7..dcbcb655c5 100644
---- a/src/main/java/net/minecraft/server/DataPaletteBlock.java
-+++ b/src/main/java/net/minecraft/server/DataPaletteBlock.java
-@@ -3,7 +3,7 @@ package net.minecraft.server;
- import com.destroystokyo.paper.antixray.ChunkPacketInfo; // Paper - Anti-Xray
- import java.util.Arrays;
- import java.util.Objects;
--import java.util.concurrent.locks.ReentrantLock;
-+import java.util.concurrent.locks.ReentrantReadWriteLock;
- import java.util.function.Function;
- import java.util.stream.Collectors;
-
-@@ -21,26 +21,43 @@ public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
- protected DataBits a; protected DataBits getDataBits() { return this.a; } // Paper - OBFHELPER
- private DataPalette<T> h; private DataPalette<T> getDataPalette() { return this.h; } // Paper - OBFHELPER
- private int i; private int getBitsPerObject() { return this.i; } // Paper - OBFHELPER
-- private final ReentrantLock j = new ReentrantLock();
-+ // Paper start - use read write locks only during generation, disable once back on main thread
-+ private static final NoopLock NOOP_LOCK = new NoopLock();
-+ private java.util.concurrent.locks.Lock readLock = NOOP_LOCK;
-+ private java.util.concurrent.locks.Lock writeLock = NOOP_LOCK;
-+
-+ private static class NoopLock extends ReentrantReadWriteLock.WriteLock {
-+ private NoopLock() {
-+ super(new ReentrantReadWriteLock());
-+ }
-+
-+ @Override
-+ public final void lock() {
-+ }
-+
-+ @Override
-+ public final void unlock() {
-
-- private void b() {
-- if (this.j.isLocked() && !this.j.isHeldByCurrentThread()) {
-- String s = (String) Thread.getAllStackTraces().keySet().stream().filter(Objects::nonNull).map((thread) -> {
-- return thread.getName() + ": \n\tat " + (String) Arrays.stream(thread.getStackTrace()).map(Object::toString).collect(Collectors.joining("\n\tat "));
-- }).collect(Collectors.joining("\n"));
-- CrashReport crashreport = new CrashReport("Writing into PalettedContainer from multiple threads", new IllegalStateException());
-- CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Thread dumps");
--
-- crashreportsystemdetails.a("Thread dumps", (Object) s);
-- throw new ReportedException(crashreport);
-- } else {
-- this.j.lock();
- }
- }
-
-+ synchronized void enableLocks() {
-+ ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
-+ readLock = lock.readLock();
-+ writeLock = lock.writeLock();
-+ }
-+ synchronized void disableLocks() {
-+ readLock = NOOP_LOCK;
-+ writeLock = NOOP_LOCK;
-+ }
-+
-+ private void b() {
-+ writeLock.lock();
-+ }
- private void c() {
-- this.j.unlock();
-+ writeLock.unlock();
- }
-+ // Paper end
-
- public DataPaletteBlock(DataPalette<T> datapalette, RegistryBlockID<T> registryblockid, Function<NBTTagCompound, T> function, Function<T, NBTTagCompound> function1, T t0) {
- // Paper start - Anti-Xray - Support default constructor
-@@ -155,9 +172,13 @@ public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
- }
-
- protected T a(int i) {
-- T t0 = this.h.a(this.a.a(i));
--
-- return t0 == null ? this.g : t0;
-+ try { // Paper start - read lock
-+ readLock.lock();
-+ T object = this.h.a(this.a.a(i)); // Paper - decompile fix
-+ return (T)(object == null ? this.g : object);
-+ } finally {
-+ readLock.unlock();
-+ } // Paper end
- }
-
- // Paper start - Anti-Xray - Support default methods
-diff --git a/src/main/java/net/minecraft/server/DefinedStructureManager.java b/src/main/java/net/minecraft/server/DefinedStructureManager.java
-index f5a6387f27..f456850997 100644
---- a/src/main/java/net/minecraft/server/DefinedStructureManager.java
-+++ b/src/main/java/net/minecraft/server/DefinedStructureManager.java
-@@ -21,7 +21,7 @@ import org.apache.logging.log4j.Logger;
- public class DefinedStructureManager implements IResourcePackListener {
-
- private static final Logger a = LogManager.getLogger();
-- private final Map<MinecraftKey, DefinedStructure> b = Maps.newHashMap();
-+ private final Map<MinecraftKey, DefinedStructure> b = Maps.newConcurrentMap(); // Paper
- private final DataFixer c;
- private final MinecraftServer d;
- private final java.nio.file.Path e;
-diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java
-index 3d90fdb642..d6d0dd6d88 100644
---- a/src/main/java/net/minecraft/server/Entity.java
-+++ b/src/main/java/net/minecraft/server/Entity.java
-@@ -207,7 +207,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
- this.random = SHARED_RANDOM; // Paper
- this.fireTicks = -this.getMaxFireTicks();
- this.justCreated = true;
-- this.uniqueID = MathHelper.a(this.random);
-+ this.uniqueID = MathHelper.a(java.util.concurrent.ThreadLocalRandom.current()); // Paper
- this.au = this.uniqueID.toString();
- this.aJ = Sets.newHashSet();
- this.aL = new double[] { 0.0D, 0.0D, 0.0D};
-diff --git a/src/main/java/net/minecraft/server/IChunkLoader.java b/src/main/java/net/minecraft/server/IChunkLoader.java
-index 4698ee99f8..431f4ab189 100644
---- a/src/main/java/net/minecraft/server/IChunkLoader.java
-+++ b/src/main/java/net/minecraft/server/IChunkLoader.java
-@@ -6,6 +6,9 @@ import javax.annotation.Nullable;
-
- public interface IChunkLoader {
-
-+ void getPersistentStructureLegacy(DimensionManager dimensionmanager, @Nullable PersistentCollection persistentcollection); // Paper
-+ void loadEntities(NBTTagCompound nbttagcompound, Chunk chunk); // Paper - Async Chunks
-+ Object[] loadChunk(GeneratorAccess generatoraccess, int i, int j, Consumer<Chunk> consumer) throws IOException; // Paper - Async Chunks
- @Nullable
- Chunk a(GeneratorAccess generatoraccess, int i, int j, Consumer<Chunk> consumer) throws IOException;
-
-diff --git a/src/main/java/net/minecraft/server/MathHelper.java b/src/main/java/net/minecraft/server/MathHelper.java
-index 8bb2593aa9..67bb289545 100644
---- a/src/main/java/net/minecraft/server/MathHelper.java
-+++ b/src/main/java/net/minecraft/server/MathHelper.java
-@@ -7,7 +7,7 @@ import java.util.function.IntPredicate;
- public class MathHelper {
-
- public static final float a = c(2.0F);
-- private static final float[] b = (float[]) SystemUtils.a((Object) (new float[65536]), (afloat) -> {
-+ private static final float[] b = (float[]) SystemUtils.a((new float[65536]), (afloat) -> { // Paper - Decompile fix
- for (int i = 0; i < afloat.length; ++i) {
- afloat[i] = (float) Math.sin((double) i * 3.141592653589793D * 2.0D / 65536.0D);
- }
-@@ -136,6 +136,7 @@ public class MathHelper {
- return Math.floorMod(i, j);
- }
-
-+ public static float normalizeYaw(float fx) { return g(fx); } // Paper - OBFHELPER
- public static float g(float f) {
- f %= 360.0F;
- if (f >= 180.0F) {
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index d2ee4e5781..236fbafeb5 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -499,6 +499,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
-
- // CraftBukkit start - fire WorldLoadEvent and handle whether or not to keep the spawn in memory
- Stopwatch stopwatch = Stopwatch.createStarted();
-+ boolean waitForChunks = Boolean.getBoolean("paper.waitforchunks"); // Paper
- for (WorldServer worldserver : this.getWorlds()) {
- MinecraftServer.LOGGER.info("Preparing start region for level " + worldserver.dimension + " (Seed: " + worldserver.getSeed() + ")");
- if (!worldserver.getWorld().getKeepSpawnInMemory()) {
-@@ -506,29 +507,24 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
- }
-
- BlockPosition blockposition = worldserver.getSpawn();
-- List<ChunkCoordIntPair> list = Lists.newArrayList();
-+ List<ChunkCoordIntPair> list = worldserver.getChunkProvider().getSpiralOutChunks(blockposition, worldserver.paperConfig.keepLoadedRange >> 4); // Paper
- Set<ChunkCoordIntPair> set = Sets.newConcurrentHashSet();
-
-- // Paper start
-- short radius = worldserver.paperConfig.keepLoadedRange;
-- for (int i = -radius; i <= radius && this.isRunning(); i += 16) {
-- for (int j = -radius; j <= radius && this.isRunning(); j += 16) {
-- // Paper end
-- list.add(new ChunkCoordIntPair(blockposition.getX() + i >> 4, blockposition.getZ() + j >> 4));
-- }
-- } // Paper
-+ // Paper - remove arraylist creation, call spiral above
- if (this.isRunning()) { // Paper
- int expected = list.size(); // Paper
-
--
-- CompletableFuture completablefuture = worldserver.getChunkProvider().a((Iterable) list, (chunk) -> {
-+ CompletableFuture completablefuture = worldserver.getChunkProvider().loadAllChunks(list, (chunk) -> { // Paper
- set.add(chunk.getPos());
-- if (set.size() < expected && set.size() % 25 == 0) this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
-+ if (waitForChunks && (set.size() == expected || (set.size() < expected && set.size() % (set.size() / 10) == 0))) {
-+ this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
-+ }
- });
-
-- while (!completablefuture.isDone()) {
-+ while (waitForChunks && !completablefuture.isDone() && isRunning()) { // Paper
- try {
-- completablefuture.get(1L, TimeUnit.SECONDS);
-+ PaperAsyncChunkProvider.processMainThreadQueue(this); // Paper
-+ completablefuture.get(50L, TimeUnit.MILLISECONDS); // Paper
- } catch (InterruptedException interruptedexception) {
- throw new RuntimeException(interruptedexception);
- } catch (ExecutionException executionexception) {
-@@ -538,11 +534,11 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
-
- throw new RuntimeException(executionexception.getCause());
- } catch (TimeoutException timeoutexception) {
-- this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
-+ //this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
- }
- }
-
-- this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
-+ if (waitForChunks) this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
- }
- }
-
-@@ -646,6 +642,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
- if (hasStopped) return;
- hasStopped = true;
- }
-+ PaperAsyncChunkProvider.stop(this); // Paper
- // CraftBukkit end
- MinecraftServer.LOGGER.info("Stopping server");
- MinecraftTimings.stopServer(); // Paper
-@@ -1013,6 +1010,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
- while ((futuretask = (FutureTask) this.f.poll()) != null) {
- SystemUtils.a(futuretask, MinecraftServer.LOGGER);
- }
-+ PaperAsyncChunkProvider.processMainThreadQueue(this); // Paper
- MinecraftTimings.minecraftSchedulerTimer.stopTiming(); // Paper
-
- this.methodProfiler.exitEnter("commandFunctions");
-@@ -1049,6 +1047,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
- // CraftBukkit - dropTickTime
- for (Iterator iterator = this.getWorlds().iterator(); iterator.hasNext();) {
- WorldServer worldserver = (WorldServer) iterator.next();
-+ PaperAsyncChunkProvider.processMainThreadQueue(worldserver); // Paper
- worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
- TileEntityHopper.skipHopperEvents = worldserver.paperConfig.disableHopperMoveEvents || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper
- i = SystemUtils.getMonotonicNanos();
-diff --git a/src/main/java/net/minecraft/server/PaperAsyncChunkProvider.java b/src/main/java/net/minecraft/server/PaperAsyncChunkProvider.java
-new file mode 100644
-index 0000000000..851756d65e
---- /dev/null
-+++ b/src/main/java/net/minecraft/server/PaperAsyncChunkProvider.java
-@@ -0,0 +1,658 @@
-+/*
-+ * This file is licensed under the MIT License (MIT).
-+ *
-+ * Copyright (c) 2018 Daniel Ennis <http://aikar.co>
-+ *
-+ * Permission is hereby granted, free of charge, to any person obtaining a copy
-+ * of this software and associated documentation files (the "Software"), to deal
-+ * in the Software without restriction, including without limitation the rights
-+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-+ * copies of the Software, and to permit persons to whom the Software is
-+ * furnished to do so, subject to the following conditions:
-+ *
-+ * The above copyright notice and this permission notice shall be included in
-+ * all copies or substantial portions of the Software.
-+ *
-+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-+ * THE SOFTWARE.
-+ */
-+package net.minecraft.server;
-+
-+import com.destroystokyo.paper.PaperConfig;
-+import com.destroystokyo.paper.util.PriorityQueuedExecutor;
-+import com.destroystokyo.paper.util.PriorityQueuedExecutor.Priority;
-+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
-+import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
-+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
-+import org.bukkit.Bukkit;
-+import org.bukkit.craftbukkit.generator.CustomChunkGenerator;
-+import org.bukkit.craftbukkit.generator.InternalChunkGenerator;
-+
-+import javax.annotation.Nullable;
-+import java.io.IOException;
-+import java.util.ArrayList;
-+import java.util.Iterator;
-+import java.util.List;
-+import java.util.concurrent.CompletableFuture;
-+import java.util.concurrent.ConcurrentLinkedDeque;
-+import java.util.concurrent.atomic.AtomicBoolean;
-+import java.util.concurrent.atomic.AtomicInteger;
-+import java.util.function.Consumer;
-+
-+@SuppressWarnings("unused")
-+public class PaperAsyncChunkProvider extends ChunkProviderServer {
-+
-+ private static final int GEN_THREAD_PRIORITY = Integer.getInteger("paper.genThreadPriority", 3);
-+ private static final int LOAD_THREAD_PRIORITY = Integer.getInteger("paper.loadThreadPriority", 4);
-+ private static final PriorityQueuedExecutor EXECUTOR = new PriorityQueuedExecutor("PaperChunkLoader", PaperConfig.asyncChunks ? PaperConfig.asyncChunkLoadThreads : 0, LOAD_THREAD_PRIORITY);
-+ private static final PriorityQueuedExecutor SINGLE_GEN_EXECUTOR = new PriorityQueuedExecutor("PaperChunkGenerator", PaperConfig.asyncChunks && PaperConfig.asyncChunkGeneration && !PaperConfig.asyncChunkGenThreadPerWorld ? 1 : 0, GEN_THREAD_PRIORITY);
-+ private static final ConcurrentLinkedDeque<Runnable> MAIN_THREAD_QUEUE = new ConcurrentLinkedDeque<>();
-+
-+ private final PriorityQueuedExecutor generationExecutor;
-+ //private static final PriorityQueuedExecutor generationExecutor = new PriorityQueuedExecutor("PaperChunkGen", 1);
-+ private final Long2ObjectMap<PendingChunk> pendingChunks = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>());
-+ private final IAsyncTaskHandler asyncHandler;
-+
-+ private final WorldServer world;
-+ private final IChunkLoader chunkLoader;
-+ private final MinecraftServer server;
-+ private final boolean shouldGenSync;
-+
-+ public PaperAsyncChunkProvider(WorldServer world, IChunkLoader chunkLoader, InternalChunkGenerator generator, MinecraftServer server) {
-+ super(world, chunkLoader, generator, server);
-+
-+ this.server = world.getMinecraftServer();
-+ this.world = world;
-+ this.asyncHandler = server;
-+ this.chunkLoader = chunkLoader;
-+ String worldName = this.world.getWorld().getName();
-+ this.shouldGenSync = generator instanceof CustomChunkGenerator && !(((CustomChunkGenerator) generator).asyncSupported) || !PaperConfig.asyncChunkGeneration;
-+ this.generationExecutor = PaperConfig.asyncChunkGenThreadPerWorld ? new PriorityQueuedExecutor("PaperChunkGen-" + worldName, shouldGenSync ? 0 : 1, GEN_THREAD_PRIORITY) : SINGLE_GEN_EXECUTOR;
-+ }
-+
-+ private static Priority calculatePriority(boolean isBlockingMain, boolean priority) {
-+ if (isBlockingMain) {
-+ return Priority.URGENT;
-+ }
-+
-+ if (priority) {
-+ return Priority.HIGH;
-+ }
-+
-+ return Priority.NORMAL;
-+ }
-+
-+ static void stop(MinecraftServer server) {
-+ for (WorldServer world : server.getWorlds()) {
-+ world.getPlayerChunkMap().shutdown();
-+ }
-+ }
-+
-+ static void processMainThreadQueue(MinecraftServer server) {
-+ for (WorldServer world : server.getWorlds()) {
-+ processMainThreadQueue(world);
-+ }
-+ }
-+
-+ static void processMainThreadQueue(World world) {
-+ IChunkProvider chunkProvider = world.getChunkProvider();
-+ if (chunkProvider instanceof PaperAsyncChunkProvider) {
-+ ((PaperAsyncChunkProvider) chunkProvider).processMainThreadQueue();
-+ }
-+ }
-+
-+ private void processMainThreadQueue() {
-+ processMainThreadQueue((PendingChunk) null);
-+ }
-+ private boolean processMainThreadQueue(PendingChunk pending) {
-+ Runnable run;
-+ boolean hadLoad = false;
-+ while ((run = MAIN_THREAD_QUEUE.poll()) != null) {
-+ run.run();
-+ hadLoad = true;
-+ if (pending != null && pending.hasPosted) {
-+ break;
-+ }
-+ }
-+ return hadLoad;
-+ }
-+
-+ @Override
-+ public void bumpPriority(ChunkCoordIntPair coords) {
-+ final PendingChunk pending = pendingChunks.get(coords.asLong());
-+ if (pending != null) {
-+ pending.bumpPriority(Priority.HIGH);
-+ }
-+ }
-+
-+ @Nullable
-+ @Override
-+ public Chunk getChunkAt(int x, int z, boolean load, boolean gen) {
-+ return getChunkAt(x, z, load, gen, null);
-+ }
-+
-+ @Nullable
-+ @Override
-+ public Chunk getChunkAt(int x, int z, boolean load, boolean gen, boolean priority, Consumer<Chunk> consumer) {
-+ final long key = ChunkCoordIntPair.asLong(x, z);
-+ final Chunk chunk = this.chunks.get(key);
-+ if (chunk != null || !load) { // return null if we aren't loading
-+ if (consumer != null) {
-+ consumer.accept(chunk);
-+ }
-+ return chunk;
-+ }
-+ return loadOrGenerateChunk(x, z, gen, priority, consumer); // Async overrides this method
-+ }
-+
-+ private Chunk loadOrGenerateChunk(int x, int z, boolean gen, boolean priority, Consumer<Chunk> consumer) {
-+ return requestChunk(x, z, gen, priority, consumer).getChunk();
-+ }
-+
-+ final PendingChunkRequest requestChunk(int x, int z, boolean gen, boolean priority, Consumer<Chunk> consumer) {
-+ try (co.aikar.timings.Timing timing = world.timings.syncChunkLoadTimer.startTiming()) {
-+ final long key = ChunkCoordIntPair.asLong(x, z);
-+ final boolean isChunkThread = isChunkThread();
-+ final boolean isBlockingMain = consumer == null && server.isMainThread();
-+ final boolean loadOnThisThread = isChunkThread || isBlockingMain;
-+ final Priority taskPriority = calculatePriority(isBlockingMain, priority);
-+
-+ // Obtain a PendingChunk
-+ final PendingChunk pending;
-+ synchronized (pendingChunks) {
-+ PendingChunk pendingChunk = pendingChunks.get(key);
-+ if (pendingChunk == null) {
-+ pending = new PendingChunk(x, z, key, gen, taskPriority);
-+ pendingChunks.put(key, pending);
-+ } else if (pendingChunk.hasFinished && gen && !pendingChunk.canGenerate && pendingChunk.chunk == null) {
-+ // need to overwrite the old
-+ pending = new PendingChunk(x, z, key, true, taskPriority);
-+ pendingChunks.put(key, pending);
-+ } else {
-+ pending = pendingChunk;
-+ if (pending.taskPriority != taskPriority) {
-+ pending.bumpPriority(taskPriority);
-+ }
-+ }
-+ }
-+
-+ // Listen for when result is ready
-+ final CompletableFuture<Chunk> future = new CompletableFuture<>();
-+ final PendingChunkRequest request = pending.addListener(future, gen, !loadOnThisThread);
-+
-+ // Chunk Generation can trigger Chunk Loading, those loads may need to convert, and could be slow
-+ // Give an opportunity for urgent tasks to jump in at these times
-+ if (isChunkThread) {
-+ processUrgentTasks();
-+ }
-+
-+ if (loadOnThisThread) {
-+ // do loads on main if blocking, or on current if we are a load/gen thread
-+ // gen threads do trigger chunk loads
-+ pending.loadTask.run();
-+ }
-+
-+ if (isBlockingMain) {
-+ while (!future.isDone()) {
-+ // We aren't done, obtain lock on queue
-+ synchronized (MAIN_THREAD_QUEUE) {
-+ // We may of received our request now, check it
-+ if (processMainThreadQueue(pending)) {
-+ // If we processed SOMETHING, don't wait
-+ continue;
-+ }
-+ try {
-+ // We got nothing from the queue, wait until something has been added
-+ MAIN_THREAD_QUEUE.wait(1);
-+ } catch (InterruptedException ignored) {
-+ }
-+ }
-+ // Queue has been notified or timed out, process it
-+ processMainThreadQueue(pending);
-+ }
-+ // We should be done AND posted into chunk map now, return it
-+ request.initialReturnChunk = pending.postChunk();
-+ } else if (consumer == null) {
-+ // This is on another thread
-+ request.initialReturnChunk = future.join();
-+ } else {
-+ future.thenAccept((c) -> this.asyncHandler.postToMainThread(() -> consumer.accept(c)));
-+ }
-+
-+ return request;
-+ }
-+ }
-+
-+ private void processUrgentTasks() {
-+ final PriorityQueuedExecutor executor = PriorityQueuedExecutor.getExecutor();
-+ if (executor != null) {
-+ executor.processUrgentTasks();
-+ }
-+ }
-+
-+ @Override
-+ public CompletableFuture<Void> loadAllChunks(Iterable<ChunkCoordIntPair> iterable, Consumer<Chunk> consumer) {
-+ final Iterator<ChunkCoordIntPair> iterator = iterable.iterator();
-+
-+ final List<CompletableFuture<Chunk>> all = new ArrayList<>();
-+ while (iterator.hasNext()) {
-+ final ChunkCoordIntPair chunkcoordintpair = iterator.next();
-+ final CompletableFuture<Chunk> future = new CompletableFuture<>();
-+ all.add(future);
-+ this.getChunkAt(chunkcoordintpair.x, chunkcoordintpair.z, true, true, chunk -> {
-+ future.complete(chunk);
-+ if (consumer != null) {
-+ consumer.accept(chunk);
-+ }
-+ });
-+ }
-+ return CompletableFuture.allOf(all.toArray(new CompletableFuture[0]));
-+ }
-+
-+ boolean chunkGoingToExists(int x, int z) {
-+ synchronized (pendingChunks) {
-+ PendingChunk pendingChunk = pendingChunks.get(ChunkCoordIntPair.asLong(x, z));
-+ return pendingChunk != null && pendingChunk.canGenerate;
-+ }
-+ }
-+
-+ private enum PendingStatus {
-+ /**
-+ * Request has just started
-+ */
-+ STARTED,
-+ /**
-+ * Chunk is attempting to be loaded from disk
-+ */
-+ LOADING,
-+ /**
-+ * Chunk must generate on main and is pending main
-+ */
-+ GENERATION_PENDING,
-+ /**
-+ * Chunk is generating
-+ */
-+ GENERATING,
-+ /**
-+ * Chunk is ready and is pending post to main
-+ */
-+ PENDING_MAIN,
-+ /**
-+ * Could not load chunk, and did not need to generat
-+ */
-+ FAIL,
-+ /**
-+ * Fully done with this request (may or may not of loaded)
-+ */
-+ DONE,
-+ /**
-+ * Chunk load was cancelled (no longer needed)
-+ */
-+ CANCELLED
-+ }
-+
-+ public interface CancellableChunkRequest {
-+ void cancel();
-+ Chunk getChunk();
-+ }
-+
-+ public static class PendingChunkRequest implements CancellableChunkRequest {
-+ private final PendingChunk pending;
-+ private final AtomicBoolean cancelled = new AtomicBoolean(false);
-+ private volatile boolean generating;
-+ private volatile Chunk initialReturnChunk;
-+
-+ private PendingChunkRequest(PendingChunk pending) {
-+ this.pending = pending;
-+ this.cancelled.set(true);
-+ }
-+
-+ private PendingChunkRequest(PendingChunk pending, boolean gen) {
-+ this.pending = pending;
-+ this.generating = gen;
-+ }
-+
-+ public void cancel() {
-+ this.pending.cancel(this);
-+ }
-+
-+ /**
-+ * Will be null on asynchronous loads
-+ */
-+ @Override @Nullable
-+ public Chunk getChunk() {
-+ return initialReturnChunk;
-+ }
-+ }
-+
-+ private boolean isLoadThread() {
-+ return EXECUTOR.isCurrentThread();
-+ }
-+
-+ private boolean isGenThread() {
-+ return generationExecutor.isCurrentThread();
-+ }
-+ private boolean isChunkThread() {
-+ return isLoadThread() || isGenThread();
-+ }
-+
-+ private class PendingChunk implements Runnable {
-+ private final int x;
-+ private final int z;
-+ private final long key;
-+ private final long started = System.currentTimeMillis();
-+ private final CompletableFuture<Chunk> loadOnly = new CompletableFuture<>();
-+ private final CompletableFuture<Chunk> generate = new CompletableFuture<>();
-+ private final AtomicInteger requests = new AtomicInteger(0);
-+
-+ private volatile PendingStatus status = PendingStatus.STARTED;
-+ private volatile PriorityQueuedExecutor.PendingTask<Void> loadTask;
-+ private volatile PriorityQueuedExecutor.PendingTask<Chunk> genTask;
-+ private volatile Priority taskPriority;
-+ private volatile boolean generating;
-+ private volatile boolean canGenerate;
-+ private volatile boolean isHighPriority;
-+ private volatile boolean hasPosted;
-+ private volatile boolean hasFinished;
-+ private volatile Chunk chunk;
-+ private volatile NBTTagCompound pendingLevel;
-+
-+ PendingChunk(int x, int z, long key, boolean canGenerate, boolean priority) {
-+ this.x = x;
-+ this.z = z;
-+ this.key = key;
-+ this.canGenerate = canGenerate;
-+ taskPriority = priority ? Priority.HIGH : Priority.NORMAL;
-+ }
-+
-+ PendingChunk(int x, int z, long key, boolean canGenerate, Priority taskPriority) {
-+ this.x = x;
-+ this.z = z;
-+ this.key = key;
-+ this.canGenerate = canGenerate;
-+ this.taskPriority = taskPriority;
-+ }
-+
-+ private synchronized void setStatus(PendingStatus status) {
-+ this.status = status;
-+ }
-+
-+ private Chunk loadChunk(int x, int z) throws IOException {
-+ setStatus(PendingStatus.LOADING);
-+ Object[] data = chunkLoader.loadChunk(world, x, z, null);
-+ if (data != null) {
-+ // Level must be loaded on main
-+ this.pendingLevel = ((NBTTagCompound) data[1]).getCompound("Level");
-+ return (Chunk) data[0];
-+ } else {
-+ return null;
-+ }
-+ }
-+
-+ private Chunk generateChunk() {
-+ synchronized (this) {
-+ if (requests.get() <= 0) {
-+ return null;
-+ }
-+ }
-+
-+ try {
-+ CompletableFuture<Chunk> pending = new CompletableFuture<>();
-+ batchScheduler.startBatch();
-+ batchScheduler.add(new ChunkCoordIntPair(x, z));
-+
-+ ProtoChunk protoChunk = batchScheduler.executeBatch().join();
-+ boolean saved = false;
-+ if (!Bukkit.isPrimaryThread()) {
-+ // If we are async, dispatch later
-+ try {
-+ chunkLoader.saveChunk(world, protoChunk, true);
-+ saved = true;
-+ } catch (IOException | ExceptionWorldConflict e) {
-+ e.printStackTrace();
-+ }
-+ }
-+ Chunk chunk = new Chunk(world, protoChunk, x, z);
-+ if (saved) {
-+ chunk.setLastSaved(world.getTime());
-+ }
-+ generateFinished(chunk);
-+
-+ return chunk;
-+ } catch (Throwable e) {
-+ MinecraftServer.LOGGER.error("Couldn't generate chunk (" +world.getWorld().getName() + ":" + x + "," + z + ")", e);
-+ generateFinished(null);
-+ return null;
-+ }
-+ }
-+
-+ boolean loadFinished(Chunk chunk) {
-+ if (chunk != null) {
-+ postChunkToMain(chunk);
-+ return false;
-+ }
-+ loadOnly.complete(null);
-+
-+ synchronized (this) {
-+ boolean cancelled = requests.get() <= 0;
-+ if (!canGenerate || cancelled) {
-+ if (!cancelled) {
-+ setStatus(PendingStatus.FAIL);
-+ }
-+ this.chunk = null;
-+ this.hasFinished = true;
-+ pendingChunks.remove(key);
-+ return false;
-+ } else {
-+ setStatus(PendingStatus.GENERATING);
-+ generating = true;
-+ return true;
-+ }
-+ }
-+ }
-+
-+ void generateFinished(Chunk chunk) {
-+ synchronized (this) {
-+ this.chunk = chunk;
-+ this.hasFinished = true;
-+ }
-+ if (chunk != null) {
-+ postChunkToMain(chunk);
-+ } else {
-+ synchronized (this) {
-+ pendingChunks.remove(key);
-+ completeFutures(null);
-+ }
-+ }
-+ }
-+
-+ synchronized private void completeFutures(Chunk chunk) {
-+ loadOnly.complete(chunk);
-+ generate.complete(chunk);
-+ }
-+
-+ private void postChunkToMain(Chunk chunk) {
-+ synchronized (this) {
-+ setStatus(PendingStatus.PENDING_MAIN);
-+ this.chunk = chunk;
-+ this.hasFinished = true;
-+ }
-+
-+ if (server.isMainThread()) {
-+ postChunk();
-+ return;
-+ }
-+
-+ // Don't post here, even if on main, it must enter the queue so we can exit any open batch
-+ // schedulers, as post stage may trigger a new generation and cause errors
-+ synchronized (MAIN_THREAD_QUEUE) {
-+ if (this.taskPriority == Priority.URGENT) {
-+ MAIN_THREAD_QUEUE.addFirst(this::postChunk);
-+ } else {
-+ MAIN_THREAD_QUEUE.addLast(this::postChunk);
-+ }
-+ MAIN_THREAD_QUEUE.notify();
-+ }
-+ }
-+
-+ Chunk postChunk() {
-+ if (!server.isMainThread()) {
-+ throw new IllegalStateException("Must post from main");
-+ }
-+ synchronized (this) {
-+ if (hasPosted || requests.get() <= 0) { // if pending is 0, all were cancelled
-+ return chunk;
-+ }
-+ hasPosted = true;
-+ }
-+ try {
-+ if (chunk == null) {
-+ chunk = chunks.get(key);
-+ completeFutures(chunk);
-+ return chunk;
-+ }
-+ if (pendingLevel != null) {
-+ chunkLoader.loadEntities(pendingLevel, chunk);
-+ pendingLevel = null;
-+ }
-+ synchronized (chunks) {
-+ final Chunk other = chunks.get(key);
-+ if (other != null) {
-+ this.chunk = other;
-+ completeFutures(other);
-+ return other;
-+ }
-+ if (chunk != null) {
-+ chunks.put(key, chunk);
-+ }
-+ }
-+
-+ chunk.addEntities();
-+
-+ completeFutures(chunk);
-+ return chunk;
-+ } finally {
-+ pendingChunks.remove(key);
-+ setStatus(PendingStatus.DONE);
-+ }
-+ }
-+
-+ synchronized PendingChunkRequest addListener(CompletableFuture<Chunk> future, boolean gen, boolean autoSubmit) {
-+ requests.incrementAndGet();
-+ if (loadTask == null) {
-+ // Take care of a race condition in that a request could be cancelled after the synchronize
-+ // on pendingChunks, but before a listener is added, which would erase these pending tasks.
-+ genTask = generationExecutor.createPendingTask(this::generateChunk, taskPriority);
-+ loadTask = EXECUTOR.createPendingTask(this, taskPriority);
-+ if (autoSubmit) {
-+ // We will execute it outside of the synchronized context immediately after
-+ loadTask.submit();
-+ }
-+ }
-+
-+ if (hasFinished) {
-+ future.complete(chunk);
-+ return new PendingChunkRequest(this);
-+ } else if (gen) {
-+ canGenerate = true;
-+ generate.thenAccept(future::complete);
-+ } else {
-+ if (generating) {
-+ future.complete(null);
-+ return new PendingChunkRequest(this);
-+ } else {
-+ loadOnly.thenAccept(future::complete);
-+ }
-+ }
-+
-+ return new PendingChunkRequest(this, gen);
-+ }
-+
-+ @Override
-+ public void run() {
-+ try {
-+ if (!loadFinished(loadChunk(x, z))) {
-+ return;
-+ }
-+ } catch (Throwable ex) {
-+ MinecraftServer.LOGGER.error("Couldn't load chunk (" +world.getWorld().getName() + ":" + x + "," + z + ")", ex);
-+ if (ex instanceof IOException) {
-+ generateFinished(null);
-+ return;
-+ }
-+ }
-+
-+ if (shouldGenSync) {
-+ synchronized (this) {
-+ setStatus(PendingStatus.GENERATION_PENDING);
-+ if (this.taskPriority == Priority.URGENT) {
-+ MAIN_THREAD_QUEUE.addFirst(() -> generateFinished(this.generateChunk()));
-+ } else {
-+ MAIN_THREAD_QUEUE.addLast(() -> generateFinished(this.generateChunk()));
-+ }
-+
-+ }
-+ synchronized (MAIN_THREAD_QUEUE) {
-+ MAIN_THREAD_QUEUE.notify();
-+ }
-+ } else {
-+ if (isGenThread()) {
-+ // ideally we should never run into 1 chunk generating another chunk...
-+ // but if we do, let's apply same solution
-+ genTask.run();
-+ } else {
-+ genTask.submit();
-+ }
-+ }
-+ }
-+
-+ void bumpPriority() {
-+ bumpPriority(Priority.HIGH);
-+ }
-+
-+ void bumpPriority(Priority newPriority) {
-+ if (taskPriority.ordinal() >= newPriority.ordinal()) {
-+ return;
-+ }
-+
-+ this.taskPriority = newPriority;
-+ PriorityQueuedExecutor.PendingTask<Void> loadTask = this.loadTask;
-+ PriorityQueuedExecutor.PendingTask<Chunk> genTask = this.genTask;
-+ if (loadTask != null) {
-+ loadTask.bumpPriority(newPriority);
-+ }
-+ if (genTask != null) {
-+ genTask.bumpPriority(newPriority);
-+ }
-+ }
-+
-+ public synchronized boolean isCancelled() {
-+ return requests.get() <= 0;
-+ }
-+
-+ public synchronized void cancel(PendingChunkRequest request) {
-+ synchronized (pendingChunks) {
-+ if (!request.cancelled.compareAndSet(false, true)) {
-+ return;
-+ }
-+
-+ if (requests.decrementAndGet() > 0) {
-+ return;
-+ }
-+
-+ boolean c1 = genTask.cancel();
-+ boolean c2 = loadTask.cancel();
-+ loadTask = null;
-+ genTask = null;
-+ pendingChunks.remove(key);
-+ setStatus(PendingStatus.CANCELLED);
-+ }
-+ }
-+ }
-+
-+}
-diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
-index 240f590666..e4cf8548d3 100644
---- a/src/main/java/net/minecraft/server/PlayerChunk.java
-+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
-@@ -22,6 +22,52 @@ public class PlayerChunk {
- private long i;
- private boolean done;
- boolean chunkExists; // Paper
-+ // Paper start
-+ PaperAsyncChunkProvider.CancellableChunkRequest chunkRequest;
-+ private java.util.function.Consumer<Chunk> chunkLoadedConsumer = chunk -> {
-+ chunkRequest = null;
-+ PlayerChunk pChunk = PlayerChunk.this;
-+ pChunk.chunk = chunk;
-+ markChunkUsed(); // Paper - delay chunk unloads
-+ };
-+ private boolean markedHigh = false;
-+ void checkHighPriority(EntityPlayer player) {
-+ if (done || markedHigh || chunk != null) {
-+ return;
-+ }
-+ final double dist = getDistance(player.locX, player.locZ);
-+ if (dist > 8) {
-+ return;
-+ }
-+ if (dist >= 3 && getDistance(player, 5) > 3.5) {
-+ return;
-+ }
-+
-+ markedHigh = true;
-+ playerChunkMap.getWorld().getChunkProvider().bumpPriority(location);
-+ if (chunkRequest == null) {
-+ requestChunkIfNeeded(PlayerChunkMap.CAN_GEN_CHUNKS.test(player));
-+ }
-+ }
-+ private void requestChunkIfNeeded(boolean flag) {
-+ if (chunkRequest == null) {
-+ chunkRequest = this.playerChunkMap.getWorld().getChunkProvider().requestChunk(this.location.x, this.location.z, flag, markedHigh, chunkLoadedConsumer);
-+ this.chunk = chunkRequest.getChunk(); // Paper)
-+ markChunkUsed(); // Paper - delay chunk unloads
-+ }
-+ }
-+ private double getDistance(EntityPlayer player, int inFront) {
-+ final float yaw = MathHelper.normalizeYaw(player.yaw);
-+ final double x = player.locX + (inFront * Math.cos(Math.toRadians(yaw)));
-+ final double z = player.locZ + (inFront * Math.sin(Math.toRadians(yaw)));
-+ return getDistance(x, z);
-+ }
-+
-+ private double getDistance(double blockX, double blockZ) {
-+ final double x = location.x - ((int)Math.floor(blockX) >> 4);
-+ final double z = location.z - ((int)Math.floor(blockZ) >> 4);
-+ return Math.sqrt((x * x) + (z * z));
-+ }
-
- public PlayerChunk(PlayerChunkMap playerchunkmap, int i, int j) {
- this.playerChunkMap = playerchunkmap;
-@@ -29,13 +75,17 @@ public class PlayerChunk {
- ChunkProviderServer chunkproviderserver = playerchunkmap.getWorld().getChunkProvider();
-
- chunkproviderserver.a(i, j);
-- this.chunk = chunkproviderserver.getChunkAt(i, j, true, false);
-- this.chunkExists = this.chunk != null || org.bukkit.craftbukkit.chunkio.ChunkIOExecutor.hasQueuedChunkLoad(playerChunkMap.getWorld(), i, j); // Paper
-+ this.chunk = chunkproviderserver.getChunkAt(i, j, false, false); // Paper
-+ this.chunkExists = this.chunk != null || chunkproviderserver.chunkGoingToExists(i, j); // Paper
- markChunkUsed(); // Paper - delay chunk unloads
- }
-
- // Paper start
- private void markChunkUsed() {
-+ if (!chunkHasPlayers && chunkRequest != null) {
-+ chunkRequest.cancel();
-+ chunkRequest = null;
-+ }
- if (chunk == null) {
- return;
- }
-@@ -65,7 +115,7 @@ public class PlayerChunk {
- this.players.add(entityplayer);
- if (this.done) {
- this.sendChunk(entityplayer);
-- }
-+ } else checkHighPriority(entityplayer); // Paper
-
- }
- }
-@@ -90,8 +140,9 @@ public class PlayerChunk {
- if (this.chunk != null) {
- return true;
- } else {
-- this.chunk = this.playerChunkMap.getWorld().getChunkProvider().getChunkAt(this.location.x, this.location.z, true, flag);
-- markChunkUsed(); // Paper - delay chunk unloads
-+ // Paper start - async chunks
-+ requestChunkIfNeeded(flag);
-+ // Paper end
- return this.chunk != null;
- }
- }
-diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
-index 679488a3cf..b7dda8e282 100644
---- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
-+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
-@@ -25,10 +25,10 @@ public class PlayerChunkMap {
- };
- private static final Predicate<EntityPlayer> b = (entityplayer) -> {
- return entityplayer != null && (!entityplayer.isSpectator() || entityplayer.getWorldServer().getGameRules().getBoolean("spectatorsGenerateChunks"));
-- };
-+ }; static final Predicate<EntityPlayer> CAN_GEN_CHUNKS = b; // Paper - OBFHELPER
- private final WorldServer world;
- private final List<EntityPlayer> managedPlayers = Lists.newArrayList();
-- private final Long2ObjectMap<PlayerChunk> e = new Long2ObjectOpenHashMap(4096);
-+ private final Long2ObjectMap<PlayerChunk> e = new Long2ObjectOpenHashMap(4096); Long2ObjectMap<PlayerChunk> getChunks() { return e; } // Paper - OBFHELPER
- private final Set<PlayerChunk> f = Sets.newHashSet();
- private final List<PlayerChunk> g = Lists.newLinkedList();
- private final List<PlayerChunk> h = Lists.newLinkedList();
-@@ -341,7 +341,13 @@ public class PlayerChunkMap {
- if (playerchunk != null) {
- playerchunk.b(entityplayer);
- }
-+ } else { // Paper start
-+ PlayerChunk playerchunk = this.getChunk(l1 - j1, i2 - k1);
-+ if (playerchunk != null) {
-+ playerchunk.checkHighPriority(entityplayer); // Paper
-+ }
- }
-+ // Paper end
- }
- }
-
-@@ -352,7 +358,11 @@ public class PlayerChunkMap {
- // CraftBukkit start - send nearest chunks first
- Collections.sort(chunksToLoad, new ChunkCoordComparator(entityplayer));
- for (ChunkCoordIntPair pair : chunksToLoad) {
-- this.c(pair.x, pair.z).a(entityplayer);
-+ // Paper start
-+ PlayerChunk c = this.c(pair.x, pair.z);
-+ c.checkHighPriority(entityplayer);
-+ c.a(entityplayer);
-+ // Paper end
- }
- // CraftBukkit end
- }
-@@ -424,6 +434,15 @@ public class PlayerChunkMap {
- }
- }
- }
-+
-+ void shutdown() {
-+ getChunks().values().forEach(pchunk -> {
-+ PaperAsyncChunkProvider.CancellableChunkRequest chunkRequest = pchunk.chunkRequest;
-+ if (chunkRequest != null) {
-+ chunkRequest.cancel();
-+ }
-+ });
-+ }
- // Paper end
-
- private void e() {
-diff --git a/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java b/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
-index de6dd4fed7..da6df06d84 100644
---- a/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
-+++ b/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
-@@ -34,7 +34,7 @@ public class RegionLimitedWorldAccess implements GeneratorAccess {
- this.d = l;
- this.e = i;
- this.f = j;
-- this.g = world;
-+ this.g = world.regionLimited(this); // Paper
- this.h = world.getSeed();
- this.m = world.getChunkProvider().getChunkGenerator().getSettings();
- this.i = world.getSeaLevel();
-diff --git a/src/main/java/net/minecraft/server/SchedulerBatch.java b/src/main/java/net/minecraft/server/SchedulerBatch.java
-index 8e909d9caf..f214a74a29 100644
---- a/src/main/java/net/minecraft/server/SchedulerBatch.java
-+++ b/src/main/java/net/minecraft/server/SchedulerBatch.java
-@@ -10,6 +10,7 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
- private final Scheduler<K, T, R> b;
- private boolean c;
- private int d = 1000;
-+ private final java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock(true); // Paper
-
- public SchedulerBatch(Scheduler<K, T, R> scheduler) {
- this.b = scheduler;
-@@ -19,8 +20,10 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
- this.b.b();
- }
-
-+ public void startBatch() { b(); } // Paper - OBFHELPER
- public void b() {
-- if (this.c) {
-+ lock.lock(); // Paper
-+ if (false && this.c) { // Paper
- throw new RuntimeException("Batch already started.");
- } else {
- this.d = 1000;
-@@ -28,6 +31,7 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
- }
- }
-
-+ public CompletableFuture<R> add(K k0) { return a(k0); } // Paper - OBFHELPER
- public CompletableFuture<R> a(K k0) {
- if (!this.c) {
- throw new RuntimeException("Batch not properly started. Please use startBatch to create a new batch.");
-@@ -44,8 +48,14 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
- }
- }
-
-+ public CompletableFuture<R> executeBatch() { return c(); } // Paper - OBFHELPER
- public CompletableFuture<R> c() {
-- if (!this.c) {
-+ // Paper start
-+ if (!lock.isHeldByCurrentThread()) {
-+ throw new IllegalStateException("Current thread does not hold the write lock");
-+ }
-+ try {// Paper end
-+ if (false && !this.c) { // Paper
- throw new RuntimeException("Batch not properly started. Please use startBatch to create a new batch.");
- } else {
- if (this.d != 1000) {
-@@ -55,5 +65,6 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
- this.c = false;
- return this.b.c();
- }
-+ } finally { lock.unlock(); } // Paper
- }
- }
-diff --git a/src/main/java/net/minecraft/server/StructurePiece.java b/src/main/java/net/minecraft/server/StructurePiece.java
-index d9def71353..945a005e90 100644
---- a/src/main/java/net/minecraft/server/StructurePiece.java
-+++ b/src/main/java/net/minecraft/server/StructurePiece.java
-@@ -16,7 +16,7 @@ public abstract class StructurePiece {
- private EnumBlockMirror b;
- private EnumBlockRotation c;
- protected int o;
-- private static final Set<Block> d = ImmutableSet.builder().add(Blocks.NETHER_BRICK_FENCE).add(Blocks.TORCH).add(Blocks.WALL_TORCH).add(Blocks.OAK_FENCE).add(Blocks.SPRUCE_FENCE).add(Blocks.DARK_OAK_FENCE).add(Blocks.ACACIA_FENCE).add(Blocks.BIRCH_FENCE).add(Blocks.JUNGLE_FENCE).add(Blocks.LADDER).add(Blocks.IRON_BARS).build();
-+ private static final Set<Block> d = ImmutableSet.<Block>builder().add(Blocks.NETHER_BRICK_FENCE).add(Blocks.TORCH).add(Blocks.WALL_TORCH).add(Blocks.OAK_FENCE).add(Blocks.SPRUCE_FENCE).add(Blocks.DARK_OAK_FENCE).add(Blocks.ACACIA_FENCE).add(Blocks.BIRCH_FENCE).add(Blocks.JUNGLE_FENCE).add(Blocks.LADDER).add(Blocks.IRON_BARS).build(); // Paper - decompile error
-
- public StructurePiece() {}
-
-@@ -66,9 +66,11 @@ public abstract class StructurePiece {
- }
-
- public static StructurePiece a(List<StructurePiece> list, StructureBoundingBox structureboundingbox) {
-+ StructurePiece structurepiece; // Paper
-+ synchronized (list) { // Paper - synchronize main structure list
- Iterator iterator = list.iterator();
-
-- StructurePiece structurepiece;
-+ //StructurePiece structurepiece; // Paper - move up
-
- do {
- if (!iterator.hasNext()) {
-@@ -77,7 +79,7 @@ public abstract class StructurePiece {
-
- structurepiece = (StructurePiece) iterator.next();
- } while (structurepiece.d() == null || !structurepiece.d().a(structureboundingbox));
--
-+ } // Paper
- return structurepiece;
- }
-
-diff --git a/src/main/java/net/minecraft/server/StructureStart.java b/src/main/java/net/minecraft/server/StructureStart.java
-index 284e96710a..8b08efe1f4 100644
---- a/src/main/java/net/minecraft/server/StructureStart.java
-+++ b/src/main/java/net/minecraft/server/StructureStart.java
-@@ -7,7 +7,7 @@ import java.util.Random;
-
- public abstract class StructureStart {
-
-- protected final List<StructurePiece> a = Lists.newArrayList();
-+ protected final List<StructurePiece> a = java.util.Collections.synchronizedList(Lists.newArrayList()); // Paper
- protected StructureBoundingBox b;
- protected int c;
- protected int d;
-@@ -51,13 +51,14 @@ public abstract class StructureStart {
-
- protected void a(IBlockAccess iblockaccess) {
- this.b = StructureBoundingBox.a();
-+ synchronized (this.a) { // Paper - synchronize
- Iterator iterator = this.a.iterator();
-
- while (iterator.hasNext()) {
- StructurePiece structurepiece = (StructurePiece) iterator.next();
-
- this.b.b(structurepiece.d());
-- }
-+ }} // Paper
-
- }
-
-@@ -126,13 +127,14 @@ public abstract class StructureStart {
- int l = k - this.b.e;
-
- this.b.a(0, l, 0);
-+ synchronized (this.a) { // Paper - synchronize
- Iterator iterator = this.a.iterator();
-
- while (iterator.hasNext()) {
- StructurePiece structurepiece = (StructurePiece) iterator.next();
-
- structurepiece.a(0, l, 0);
-- }
-+ }} // Paper
-
- }
-
-@@ -149,13 +151,14 @@ public abstract class StructureStart {
- int i1 = l - this.b.b;
-
- this.b.a(0, i1, 0);
-+ synchronized (this.a) {
- Iterator iterator = this.a.iterator();
-
- while (iterator.hasNext()) {
- StructurePiece structurepiece = (StructurePiece) iterator.next();
-
- structurepiece.a(0, i1, 0);
-- }
-+ }} // Paper
-
- }
-
-diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
-index 0d45e21573..97a0fbd55c 100644
---- a/src/main/java/net/minecraft/server/World.java
-+++ b/src/main/java/net/minecraft/server/World.java
-@@ -41,7 +41,7 @@ import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
- import org.bukkit.generator.ChunkGenerator;
- // CraftBukkit end
-
--public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAccess, AutoCloseable {
-+public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAccess, AutoCloseable, Cloneable { // Paper
-
- protected static final Logger e = LogManager.getLogger();
- private static final EnumDirection[] a = EnumDirection.values();
-@@ -104,6 +104,24 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
- protected PersistentVillage villages;
- public final MethodProfiler methodProfiler;
- public final boolean isClientSide;
-+ // Paper start - yes this is hacky as shit
-+ RegionLimitedWorldAccess regionLimited;
-+ World originalWorld;
-+ public World regionLimited(RegionLimitedWorldAccess limitedWorldAccess) {
-+ try {
-+ World clone = (World) super.clone();
-+ clone.regionLimited = limitedWorldAccess;
-+ clone.originalWorld = this;
-+ return clone;
-+ } catch (CloneNotSupportedException e1) {
-+ }
-+ return null;
-+ }
-+ ChunkCoordIntPair[] strongholdCoords;
-+ final java.util.concurrent.atomic.AtomicBoolean
-+ strongholdInit = new java.util.concurrent.atomic.AtomicBoolean
-+ (false);
-+ // Paper end
- public boolean allowMonsters;
- public boolean allowAnimals;
- private boolean J;
-@@ -744,17 +762,42 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
-
- }
-
-- public IBlockData getType(BlockPosition blockposition) {
-- // CraftBukkit start - tree generation
-+ // Paper - async variant
-+ public java.util.concurrent.CompletableFuture<IBlockData> getTypeAsync(BlockPosition blockposition) {
-+ int x = blockposition.getX();
-+ int z = blockposition.getZ();
- if (captureTreeGeneration) {
- Iterator<CraftBlockState> it = capturedBlockStates.iterator();
- while (it.hasNext()) {
- CraftBlockState previous = it.next();
-- if (previous.getPosition().equals(blockposition)) {
-- return previous.getHandle();
-+ if (previous.getX() == x && previous.getY() == blockposition.getY() && previous.getZ() == z) {
-+ return java.util.concurrent.CompletableFuture.completedFuture(previous.getHandle());
- }
- }
- }
-+ if (blockposition.isInvalidYLocation()) {
-+ return java.util.concurrent.CompletableFuture.completedFuture(Blocks.VOID_AIR.getBlockData());
-+ } else {
-+ java.util.concurrent.CompletableFuture<IBlockData> future = new java.util.concurrent.CompletableFuture<>();
-+ ((ChunkProviderServer) chunkProvider).getChunkAt(x << 4, z << 4, true, true, (chunk) -> {
-+ future.complete(chunk.getType(blockposition));
-+ });
-+ return future;
-+ }
-+ }
-+ // Paper end
-+
-+ public IBlockData getType(BlockPosition blockposition) {
-+ // CraftBukkit start - tree generation
-+ if (captureTreeGeneration) { // If any of this logic updates, update async variant above
-+ Iterator<CraftBlockState> it = capturedBlockStates.iterator();
-+ while (it.hasNext()) { // If any of this logic updates, update async variant above
-+ CraftBlockState previous = it.next();
-+ if (previous.getX() == blockposition.getX() && previous.getY() == blockposition.getY() && previous.getZ() == blockposition.getZ()) {
-+ return previous.getHandle(); // If any of this logic updates, update async variant above
-+ }
-+ } // If any of this logic updates, update async variant above
-+ }
- // CraftBukkit end
- if (blockposition.isInvalidYLocation()) { // Paper
- return Blocks.VOID_AIR.getBlockData();
-@@ -1023,6 +1066,11 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
- }
-
- public boolean addEntity(Entity entity, SpawnReason spawnReason) { // Changed signature, added SpawnReason
-+ // Paper start
-+ if (regionLimited != null) {
-+ return regionLimited.addEntity(entity, spawnReason);
-+ }
-+ // Paper end
- org.spigotmc.AsyncCatcher.catchOp( "entity add"); // Spigot
- if (entity.valid) { MinecraftServer.LOGGER.error("Attempted Double World add on " + entity, new Throwable()); return true; } // Paper
- if (!CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) {
-diff --git a/src/main/java/net/minecraft/server/WorldGenStronghold.java b/src/main/java/net/minecraft/server/WorldGenStronghold.java
-index 69d8a25bdf..d0eaa9e9f8 100644
---- a/src/main/java/net/minecraft/server/WorldGenStronghold.java
-+++ b/src/main/java/net/minecraft/server/WorldGenStronghold.java
-@@ -10,23 +10,28 @@ import javax.annotation.Nullable;
-
- public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrongholdConfiguration> {
-
-- private boolean b;
-- private ChunkCoordIntPair[] c;
-- private long d;
-+ // Paper start - no shared state
-+ //private boolean b;
-+ //private ChunkCoordIntPair[] c;
-+ //private long d;
-+ // Paper end
-
- public WorldGenStronghold() {}
-
- protected boolean a(ChunkGenerator<?> chunkgenerator, Random random, int i, int j) {
-- if (this.d != chunkgenerator.getSeed()) {
-+ // Paper start
-+ /*if (this.d != chunkgenerator.getSeed()) {
- this.c();
-- }
-+ }*/
-
-- if (!this.b) {
-+ synchronized (chunkgenerator.getWorld().strongholdInit) {
-+ if (chunkgenerator.getWorld().strongholdInit.compareAndSet(false, true)) { // Paper
- this.a(chunkgenerator);
-- this.b = true;
-- }
-+ //this.b = true;
-+ }} // Paper
-+ // Paper end
-
-- ChunkCoordIntPair[] achunkcoordintpair = this.c;
-+ ChunkCoordIntPair[] achunkcoordintpair = chunkgenerator.getWorld().strongholdCoords; // Paper
- int k = achunkcoordintpair.length;
-
- for (int l = 0; l < k; ++l) {
-@@ -41,8 +46,8 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
- }
-
- private void c() {
-- this.b = false;
-- this.c = null;
-+ //this.b = false; // Paper
-+ //this.c = null; // Paper
- }
-
- protected boolean a(GeneratorAccess generatoraccess) {
-@@ -76,23 +81,30 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
- if (!chunkgenerator.getWorldChunkManager().a(this)) {
- return null;
- } else {
-- if (this.d != world.getSeed()) {
-- this.c();
-- }
-+ // Paper start
-+ /*if (this.d != chunkgenerator.getSeed()) {
-+ this.c();
-+ }*/
-
-- if (!this.b) {
-- this.a(chunkgenerator);
-- this.b = true;
-- }
-+ synchronized (chunkgenerator.getWorld().strongholdInit) {
-+ if (chunkgenerator.getWorld().strongholdInit.compareAndSet(false, true)) { // Paper
-+ this.a(chunkgenerator);
-+ //this.b = true;
-+ }} // Paper
-+ // Paper end
-
- BlockPosition blockposition1 = null;
- BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(0, 0, 0);
- double d0 = Double.MAX_VALUE;
-+ // Paper start
-+ /*
- ChunkCoordIntPair[] achunkcoordintpair = this.c;
- int j = achunkcoordintpair.length;
-
- for (int k = 0; k < j; ++k) {
-- ChunkCoordIntPair chunkcoordintpair = achunkcoordintpair[k];
-+ */
-+ for (ChunkCoordIntPair chunkcoordintpair : world.strongholdCoords) {
-+ // Paper end
-
- blockposition_mutableblockposition.c((chunkcoordintpair.x << 4) + 8, 32, (chunkcoordintpair.z << 4) + 8);
- double d1 = blockposition_mutableblockposition.n(blockposition);
-@@ -111,7 +123,7 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
- }
-
- private void a(ChunkGenerator<?> chunkgenerator) {
-- this.d = chunkgenerator.getSeed();
-+ //this.d = chunkgenerator.getSeed(); // Paper
- List<BiomeBase> list = Lists.newArrayList();
- Iterator iterator = IRegistry.BIOME.iterator();
-
-@@ -127,7 +139,7 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
- int j = chunkgenerator.getSettings().f();
- int k = chunkgenerator.getSettings().g();
-
-- this.c = new ChunkCoordIntPair[j];
-+ ChunkCoordIntPair[] strongholdCoords = chunkgenerator.getWorld().strongholdCoords = new ChunkCoordIntPair[j]; // Paper
- int l = 0;
- Long2ObjectMap<StructureStart> long2objectmap = chunkgenerator.getStructureStartCache(this);
-
-@@ -137,8 +149,8 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
- while (objectiterator.hasNext()) {
- StructureStart structurestart = (StructureStart) objectiterator.next();
-
-- if (l < this.c.length) {
-- this.c[l++] = new ChunkCoordIntPair(structurestart.e(), structurestart.f());
-+ if (l < strongholdCoords.length) { // Paper
-+ strongholdCoords[l++] = new ChunkCoordIntPair(structurestart.e(), structurestart.f()); // Paper
- }
- }
- }
-@@ -149,11 +161,11 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
- double d0 = random.nextDouble() * 3.141592653589793D * 2.0D;
- int i1 = long2objectmap.size();
-
-- if (i1 < this.c.length) {
-+ if (i1 < strongholdCoords.length) { // Paper
- int j1 = 0;
- int k1 = 0;
-
-- for (int l1 = 0; l1 < this.c.length; ++l1) {
-+ for (int l1 = 0; l1 < strongholdCoords.length; ++l1) { // Paper
- double d1 = (double) (4 * i + i * k1 * 6) + (random.nextDouble() - 0.5D) * (double) i * 2.5D;
- int i2 = (int) Math.round(Math.cos(d0) * d1);
- int j2 = (int) Math.round(Math.sin(d0) * d1);
-@@ -165,7 +177,7 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
- }
-
- if (l1 >= i1) {
-- this.c[l1] = new ChunkCoordIntPair(i2, j2);
-+ strongholdCoords[l1] = new ChunkCoordIntPair(i2, j2); // Paper
- }
-
- d0 += 6.283185307179586D / (double) k;
-@@ -174,7 +186,7 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
- ++k1;
- j1 = 0;
- k += 2 * k / (k1 + 1);
-- k = Math.min(k, this.c.length - l1);
-+ k = Math.min(k, strongholdCoords.length - l1); // Paper
- d0 += random.nextDouble() * 3.141592653589793D * 2.0D;
- }
- }
-diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
-index 0ff3fe03dd..e71a405807 100644
---- a/src/main/java/net/minecraft/server/WorldServer.java
-+++ b/src/main/java/net/minecraft/server/WorldServer.java
-@@ -80,6 +80,7 @@ public class WorldServer extends World implements IAsyncTaskHandler {
- this.P();
- this.Q();
- this.getWorldBorder().a(minecraftserver.au());
-+ MCUtil.scheduleAsyncTask(() -> this.getChunkProvider().chunkLoader.getPersistentStructureLegacy(dimensionmanager, worldMaps)); // Paper
- }
-
- public WorldServer i_() {
-@@ -714,7 +715,7 @@ public class WorldServer extends World implements IAsyncTaskHandler {
- gen = new org.bukkit.craftbukkit.generator.NormalChunkGenerator(this, this.getSeed());
- }
-
-- return new ChunkProviderServer(this, ichunkloader, gen, this.server);
-+ return com.destroystokyo.paper.PaperConfig.asyncChunks ? new PaperAsyncChunkProvider(this, ichunkloader, gen, this.server) : new ChunkProviderServer(this, ichunkloader, gen, this.server); // Paper - async chunks
- // CraftBukkit end
- }
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-index d4d8fbb556..af065bd705 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-@@ -1022,8 +1022,12 @@ public final class CraftServer implements Server {
- if (internal.getWorld().getKeepSpawnInMemory()) {
- short short1 = internal.paperConfig.keepLoadedRange; // Paper
- long i = System.currentTimeMillis();
-- for (int j = -short1; j <= short1; j += 16) {
-- for (int k = -short1; k <= short1; k += 16) {
-+ // Paper start
-+ for (ChunkCoordIntPair coords : internal.getChunkProvider().getSpiralOutChunks(internal.getSpawn(), short1 >> 4)) {{
-+ int j = coords.x;
-+ int k = coords.z;
-+ // Paper end
-+
- long l = System.currentTimeMillis();
-
- if (l < i) {
-@@ -1039,7 +1043,7 @@ public final class CraftServer implements Server {
- }
-
- BlockPosition chunkcoordinates = internal.getSpawn();
-- internal.getChunkProvider().getChunkAt(chunkcoordinates.getX() + j >> 4, chunkcoordinates.getZ() + k >> 4, true, true);
-+ internal.getChunkProvider().getChunkAt(chunkcoordinates.getX() + j >> 4, chunkcoordinates.getZ() + k >> 4, true, true, c -> {}); // Paper
- }
- }
- }
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 0e4455d66e..eacecccfdb 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-@@ -163,6 +163,16 @@ public class CraftWorld implements World {
- }
- }
-
-+ // Paper start - Async chunk load API
-+ @Override
-+ public java.util.concurrent.CompletableFuture<Chunk> getChunkAtAsync(final int x, final int z, final boolean gen) {
-+ final ChunkProviderServer cps = this.world.getChunkProvider();
-+ java.util.concurrent.CompletableFuture<Chunk> future = new java.util.concurrent.CompletableFuture<>();
-+ cps.getChunkAt(x, z, true, gen, chunk -> future.complete(chunk != null ? chunk.bukkitChunk : null));
-+ return future;
-+ }
-+ // Paper end
-+
- public Chunk getChunkAt(int x, int z) {
- return this.world.getChunkProvider().getChunkAt(x, z, true, true).bukkitChunk;
- }
-@@ -1466,10 +1476,13 @@ public class CraftWorld implements World {
- int chunkCoordZ = chunkcoordinates.getZ() >> 4;
- // Cycle through the 25x25 Chunks around it to load/unload the chunks.
- int radius = world.paperConfig.keepLoadedRange / 16; // Paper
-- for (int x = -radius; x <= radius; x++) { // Paper
-- for (int z = -radius; z <= radius; z++) { // Paper
-+ // Paper start
-+ for (ChunkCoordIntPair coords : world.getChunkProvider().getSpiralOutChunks(world.getSpawn(), radius)) {{
-+ int x = coords.x;
-+ int z = coords.z;
-+ // Paper end
- if (keepLoaded) {
-- loadChunk(chunkCoordX + x, chunkCoordZ + z);
-+ getChunkAtAsync(chunkCoordX + x, chunkCoordZ + z, chunk -> {}); // Paper - Async Chunks
- } else {
- if (isChunkLoaded(chunkCoordX + x, chunkCoordZ + z)) {
- unloadChunk(chunkCoordX + x, chunkCoordZ + z);
-diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
-index 68e30185a2..7905420cac 100644
---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
-+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
-@@ -82,6 +82,7 @@ public class CraftEventFactory {
- public static final DamageSource POISON = CraftDamageSource.copyOf(DamageSource.MAGIC);
- public static org.bukkit.block.Block blockDamage; // For use in EntityDamageByBlockEvent
- public static Entity entityDamage; // For use in EntityDamageByEntityEvent
-+ public static boolean isWorldGen(GeneratorAccess world) { return world instanceof net.minecraft.server.RegionLimitedWorldAccess; } // Paper
-
- // helper methods
- private static boolean canBuild(CraftWorld world, Player player, int x, int z) {
-@@ -474,6 +475,7 @@ public class CraftEventFactory {
- CraftServer craftServer = (CraftServer) entity.getServer();
-
- CreatureSpawnEvent event = new CreatureSpawnEvent(entity, spawnReason);
-+ if (isWorldGen(entityliving.world)) return event; // Paper - do not call during world gen
- craftServer.getPluginManager().callEvent(event);
- return event;
- }
-@@ -1128,6 +1130,7 @@ public class CraftEventFactory {
- }
-
- BlockIgniteEvent event = new BlockIgniteEvent(bukkitWorld.getBlockAt(block.getX(), block.getY(), block.getZ()), cause, igniter);
-+ if (isWorldGen(world)) return event; // Paper - do not call during world gen
- world.getServer().getPluginManager().callEvent(event);
- return event;
- }
-@@ -1152,6 +1155,7 @@ public class CraftEventFactory {
- }
-
- BlockIgniteEvent event = new BlockIgniteEvent(bukkitWorld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), cause, bukkitIgniter);
-+ if (isWorldGen(world)) return event; // Paper - do not call during world gen
- world.getServer().getPluginManager().callEvent(event);
- return event;
- }
-@@ -1359,7 +1363,8 @@ public class CraftEventFactory {
- public static BlockPhysicsEvent callBlockPhysicsEvent(GeneratorAccess world, BlockPosition blockposition) {
- org.bukkit.block.Block block = CraftBlock.at(world, blockposition);
- BlockPhysicsEvent event = new BlockPhysicsEvent(block, block.getBlockData());
-- world.getMinecraftWorld().getMinecraftServer().server.getPluginManager().callEvent(event);
-+ if (isWorldGen(world)) return event; // Paper - do not call during world gen
-+ Bukkit.getPluginManager().callEvent(event); // Paper
- return event;
- }
-
-@@ -1395,6 +1400,7 @@ public class CraftEventFactory {
- }
-
- EntityPotionEffectEvent event = new EntityPotionEffectEvent((LivingEntity) entity.getBukkitEntity(), bukkitOldEffect, bukkitNewEffect, cause, action, willOverride);
-+ if (isWorldGen(entity.world)) return event; // Paper - do not call during world gen
- Bukkit.getPluginManager().callEvent(event);
-
- return event;
-@@ -1413,6 +1419,7 @@ public class CraftEventFactory {
- blockState.setData(block);
-
- BlockFormEvent event = (entity == null) ? new BlockFormEvent(blockState.getBlock(), blockState) : new EntityBlockFormEvent(entity.getBukkitEntity(), blockState.getBlock(), blockState);
-+ if (isWorldGen(world)) return true; // Paper - do not call during world gen
- world.getServer().getPluginManager().callEvent(event);
-
- if (!event.isCancelled()) {
-diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
-index 9e553866eb..04c0adf7c7 100644
---- a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
-+++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
-@@ -21,6 +21,7 @@ public class CustomChunkGenerator extends InternalChunkGenerator<GeneratorSettin
- private final WorldServer world;
- private final long seed;
- private final Random random;
-+ public final boolean asyncSupported; // Paper
- private final WorldChunkManager chunkManager;
- private final WorldGenStronghold strongholdGen = new WorldGenStronghold();
- private final GeneratorSettingsDefault settings = new GeneratorSettingsDefault();
-@@ -43,6 +44,15 @@ public class CustomChunkGenerator extends InternalChunkGenerator<GeneratorSettin
- this.world = (WorldServer) world;
- this.generator = generator;
- this.seed = seed;
-+ // Paper start
-+ boolean asyncSupported = false;
-+ try {
-+ java.lang.reflect.Field asyncSafe = generator.getClass().getDeclaredField("PAPER_ASYNC_SAFE");
-+ asyncSafe.setAccessible(true);
-+ asyncSupported = asyncSafe.getBoolean(generator);
-+ } catch (NoSuchFieldException | IllegalAccessException ignored) {}
-+ this.asyncSupported = asyncSupported;
-+ // Paper end
-
- this.random = new Random(seed);
- this.chunkManager = world.worldProvider.getChunkGenerator().getWorldChunkManager();
---
-2.21.0
-