diff options
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.patch | 2503 |
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 <[email protected]> -+ */ -+@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 - |