From dc555f847818b5ca5dbb6e6f9d52a36a08058868 Mon Sep 17 00:00:00 2001 From: Aikar Date: Wed, 31 Oct 2018 23:57:03 -0400 Subject: [PATCH] Many major improvements to Async Chunk Loading Fixes some bugs with urgent priority, improves priority all around to optimize blocking chunk requests as much as possible. fixes casing on the -Dpaper.maxchunkthreads to now be -Dpaper.maxChunkThreads adds -Dpaper.genThreadPriority=3 -Dpaper.loadThreadPriority=4 lowering thread priorities will help ensure main has more priority over chunk threads --- ...1-Async-Chunk-Loading-and-Generation.patch | 255 ++++++++++-------- 1 file changed, 149 insertions(+), 106 deletions(-) diff --git a/Spigot-Server-Patches/0371-Async-Chunk-Loading-and-Generation.patch b/Spigot-Server-Patches/0371-Async-Chunk-Loading-and-Generation.patch index 3e79d56072..0af626188b 100644 --- a/Spigot-Server-Patches/0371-Async-Chunk-Loading-and-Generation.patch +++ b/Spigot-Server-Patches/0371-Async-Chunk-Loading-and-Generation.patch @@ -1,4 +1,4 @@ -From 7f80524406fbc47888a8909fb32ea6e0545bc294 Mon Sep 17 00:00:00 2001 +From d4f886d5ed2fb5a41d1f0496df0ec473f9f61cf6 Mon Sep 17 00:00:00 2001 From: Aikar Date: Sat, 21 Jul 2018 16:55:04 -0400 Subject: [PATCH] Async Chunk Loading and Generation @@ -43,7 +43,7 @@ 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 b703e0848..77d35ac99 100644 +index b703e08486..77d35ac99d 100644 --- a/src/main/java/com/destroystokyo/paper/PaperConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java @@ -385,4 +385,57 @@ public class PaperConfig { @@ -70,7 +70,7 @@ index b703e0848..77d35ac99 100644 + 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); ++ asyncChunkLoadThreads = (int) Math.min(Integer.getInteger("paper.maxChunkThreads", 8), Runtime.getRuntime().availableProcessors() * 1.5); + } + + // Let Shared Host set some limits @@ -106,15 +106,12 @@ index b703e0848..77d35ac99 100644 } 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 000000000..e589aa356 +index 0000000000..a796af2921 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/util/PriorityQueuedExecutor.java -@@ -0,0 +1,298 @@ +@@ -0,0 +1,319 @@ +package com.destroystokyo.paper.util; + -+import com.google.common.util.concurrent.ThreadFactoryBuilder; -+import net.minecraft.server.NamedIncrementingThreadFactory; -+ +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.List; @@ -122,7 +119,6 @@ index 000000000..e589aa356 +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.RejectedExecutionException; -+import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; @@ -153,12 +149,19 @@ index 000000000..e589aa356 + } + + public PriorityQueuedExecutor(String name, int threads, RejectionHandler handler) { -+ ThreadFactory factory = new ThreadFactoryBuilder() -+ .setThreadFactory(new NamedIncrementingThreadFactory(name)) -+ .setDaemon(true) -+ .build(); ++ this(name, threads, handler, Thread.NORM_PRIORITY); ++ } ++ ++ public PriorityQueuedExecutor(String name, int threads, int threadPriority) { ++ this(name, threads, null, threadPriority); ++ } ++ ++ public PriorityQueuedExecutor(String name, int threads, RejectionHandler handler, int threadPriority) { + for (int i = 0; i < threads; i++) { -+ final Thread thread = factory.newThread(this::processQueues); ++ 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); + } @@ -235,28 +238,20 @@ index 000000000..e589aa356 + } + + public PendingTask submitTask(Runnable run) { -+ return submitTask(createPendingTask(run)); ++ return createPendingTask(run).submit(); + } + + public PendingTask submitTask(Runnable run, Priority priority) { -+ return submitTask(createPendingTask(run, priority)); ++ return createPendingTask(run, priority).submit(); + } + + public PendingTask submitTask(Supplier run) { -+ return submitTask(createPendingTask(run)); ++ return createPendingTask(run).submit(); + } + + public PendingTask submitTask(Supplier run, Priority priority) { -+ return submitTask(createPendingTask(run, priority)); -+ } -+ -+ public PendingTask submitTask(PendingTask task) { -+ if (shuttingDown) { -+ handler.onRejection(task, this); -+ return task; -+ } -+ task.submit(this); -+ return task; ++ PendingTask task = createPendingTask(run, priority); ++ return task.submit(); + } + + @Override @@ -264,7 +259,19 @@ index 000000000..e589aa356 + submitTask(command); + } + -+ private Runnable getTask() { ++ 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; @@ -308,6 +315,16 @@ index 000000000..e589aa356 + NORMAL, HIGH, URGENT + } + ++ public class ExecutorThread extends Thread { ++ public ExecutorThread(Runnable runnable) { ++ super(runnable); ++ } ++ ++ public PriorityQueuedExecutor getExecutor() { ++ return PriorityQueuedExecutor.this; ++ } ++ } ++ + public class PendingTask implements Runnable { + + private final AtomicBoolean hasRan = new AtomicBoolean(); @@ -350,31 +367,35 @@ index 000000000..e589aa356 + public void bumpPriority(Priority newPriority) { + for (;;) { + int current = this.priority.get(); -+ if (current >= newPriority.ordinal()) { -+ return; -+ } -+ if (priority.compareAndSet(current, newPriority.ordinal())) { ++ int ordinal = newPriority.ordinal(); ++ if (current >= ordinal || priority.compareAndSet(current, ordinal)) { + break; + } + } + -+ if (this.executor == null) { ++ ++ if (this.submitted.get() == -1 || this.hasRan.get()) { + return; + } -+ // If we have already been submitted, resubmit with new priority -+ submit(this.executor); ++ ++ // Only resubmit if it hasnt ran yet and has been submitted ++ submit(); + } + + public CompletableFuture onDone() { + return future; + } + -+ public void submit(PriorityQueuedExecutor executor) { ++ public PendingTask 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; ++ return this; + } + if (this.submitted.compareAndSet(submitted, priority)) { + if (priority == Priority.URGENT.ordinal()) { @@ -389,11 +410,11 @@ index 000000000..e589aa356 + } + } + -+ //noinspection SynchronizationOnLocalVariableOrMethodParameter -+ synchronized (executor) { ++ synchronized (PriorityQueuedExecutor.this) { + // Wake up a thread to take this work -+ executor.notify(); ++ PriorityQueuedExecutor.this.notify(); + } ++ return this; + } + } + public interface RejectionHandler { @@ -409,7 +430,7 @@ index 000000000..e589aa356 + +} diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java -index 479a84a25..340b756bb 100644 +index 479a84a250..340b756bb4 100644 --- a/src/main/java/net/minecraft/server/Chunk.java +++ b/src/main/java/net/minecraft/server/Chunk.java @@ -184,6 +184,7 @@ public class Chunk implements IChunkAccess { @@ -421,7 +442,7 @@ index 479a84a25..340b756bb 100644 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 39ac032b0..1662e4eba 100644 +index 39ac032b0b..1662e4eba5 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 { @@ -527,7 +548,7 @@ index 39ac032b0..1662e4eba 100644 public Chunk remove(Object object) { diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java -index e64cb8051..7a1f84886 100644 +index e64cb80514..7a1f848863 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -35,12 +35,12 @@ public class ChunkProviderServer implements IChunkProvider { @@ -677,7 +698,7 @@ index e64cb8051..7a1f84886 100644 } diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java -index 9b4bc3ff6..4c22f6d75 100644 +index 9b4bc3ff68..4c22f6d756 100644 --- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java +++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java @@ -120,7 +120,7 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver { @@ -704,7 +725,7 @@ index 9b4bc3ff6..4c22f6d75 100644 completion = new Supplier() { public NBTTagCompound get() { diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java -index bdfc7d81f..a5c4564d6 100644 +index bdfc7d81ff..a5c4564d60 100644 --- a/src/main/java/net/minecraft/server/ChunkSection.java +++ b/src/main/java/net/minecraft/server/ChunkSection.java @@ -24,7 +24,17 @@ public class ChunkSection { @@ -726,7 +747,7 @@ index bdfc7d81f..a5c4564d6 100644 public IBlockData getType(int i, int j, int k) { return 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 34019bd1b..fc9091c80 100644 +index 34019bd1b3..fc9091c801 100644 --- a/src/main/java/net/minecraft/server/ChunkTaskScheduler.java +++ b/src/main/java/net/minecraft/server/ChunkTaskScheduler.java @@ -20,13 +20,14 @@ public class ChunkTaskScheduler extends Scheduler map) { diff --git a/src/main/java/net/minecraft/server/DataPaletteBlock.java b/src/main/java/net/minecraft/server/DataPaletteBlock.java -index 71a3636be..ff0fe2541 100644 +index 71a3636be6..ff0fe25417 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; @@ -882,7 +903,7 @@ index 71a3636be..ff0fe2541 100644 // 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 271dc41d4..bd15534c2 100644 +index 271dc41d45..bd15534c23 100644 --- a/src/main/java/net/minecraft/server/DefinedStructureManager.java +++ b/src/main/java/net/minecraft/server/DefinedStructureManager.java @@ -19,7 +19,7 @@ import org.apache.logging.log4j.Logger; @@ -895,7 +916,7 @@ index 271dc41d4..bd15534c2 100644 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 13c0c7ee8..552be8cd8 100644 +index 13c0c7ee89..552be8cd88 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java @@ -209,7 +209,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke @@ -908,7 +929,7 @@ index 13c0c7ee8..552be8cd8 100644 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 4698ee99f..dfb45cc4e 100644 +index 4698ee99f8..dfb45cc4ea 100644 --- a/src/main/java/net/minecraft/server/IChunkLoader.java +++ b/src/main/java/net/minecraft/server/IChunkLoader.java @@ -6,6 +6,8 @@ import javax.annotation.Nullable; @@ -921,7 +942,7 @@ index 4698ee99f..dfb45cc4e 100644 Chunk a(GeneratorAccess generatoraccess, int i, int j, Consumer consumer) throws IOException; diff --git a/src/main/java/net/minecraft/server/MathHelper.java b/src/main/java/net/minecraft/server/MathHelper.java -index 49fba0979..9ad646f8d 100644 +index 49fba0979e..9ad646f8d4 100644 --- a/src/main/java/net/minecraft/server/MathHelper.java +++ b/src/main/java/net/minecraft/server/MathHelper.java @@ -142,6 +142,7 @@ public class MathHelper { @@ -933,7 +954,7 @@ index 49fba0979..9ad646f8d 100644 fx = fx % 360.0F; if (fx >= 180.0F) { diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 763130b03..67722440f 100644 +index 763130b036..67722440fd 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -503,6 +503,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati @@ -1031,10 +1052,10 @@ index 763130b03..67722440f 100644 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 000000000..c334462f2 +index 0000000000..cb2aa9c493 --- /dev/null +++ b/src/main/java/net/minecraft/server/PaperAsyncChunkProvider.java -@@ -0,0 +1,619 @@ +@@ -0,0 +1,641 @@ +/* + * This file is licensed under the MIT License (MIT). + * @@ -1062,6 +1083,7 @@ index 000000000..c334462f2 + +import com.destroystokyo.paper.PaperConfig; +import com.destroystokyo.paper.util.PriorityQueuedExecutor; ++import com.destroystokyo.paper.util.PriorityQueuedExecutor.ExecutorThread; +import com.destroystokyo.paper.util.PriorityQueuedExecutor.Priority; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; @@ -1076,6 +1098,7 @@ index 000000000..c334462f2 +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; @@ -1084,11 +1107,11 @@ index 000000000..c334462f2 +@SuppressWarnings("unused") +public class PaperAsyncChunkProvider extends ChunkProviderServer { + -+ private static final PriorityQueuedExecutor EXECUTOR = new PriorityQueuedExecutor("PaperChunkLoader", PaperConfig.asyncChunks ? PaperConfig.asyncChunkLoadThreads : 0); -+ private static final PriorityQueuedExecutor SINGLE_GEN_EXECUTOR = new PriorityQueuedExecutor("PaperChunkGenerator", PaperConfig.asyncChunks && PaperConfig.asyncChunkGeneration && !PaperConfig.asyncChunkGenThreadPerWorld ? 1 : 0); -+ private static final ConcurrentLinkedQueue MAIN_THREAD_QUEUE = new ConcurrentLinkedQueue<>(); -+ private static final ThreadLocal IS_CHUNK_THREAD = ThreadLocal.withInitial(() -> false); -+ private static final ThreadLocal IS_CHUNK_GEN_THREAD = ThreadLocal.withInitial(() -> false); ++ 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 MAIN_THREAD_QUEUE = new ConcurrentLinkedDeque<>(); + + private final PriorityQueuedExecutor generationExecutor; + //private static final PriorityQueuedExecutor generationExecutor = new PriorityQueuedExecutor("PaperChunkGen", 1); @@ -1109,7 +1132,7 @@ index 000000000..c334462f2 + 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) : SINGLE_GEN_EXECUTOR; ++ this.generationExecutor = PaperConfig.asyncChunkGenThreadPerWorld ? new PriorityQueuedExecutor("PaperChunkGen-" + worldName, shouldGenSync ? 0 : 1, GEN_THREAD_PRIORITY) : SINGLE_GEN_EXECUTOR; + } + + static void processChunkLoads(MinecraftServer server) { @@ -1144,11 +1167,17 @@ index 000000000..c334462f2 + } + + private boolean processChunkLoads() { ++ return processChunkLoads((CompletableFuture) null); ++ } ++ private boolean processChunkLoads(CompletableFuture pendingRequest) { + Runnable run; + boolean hadLoad = false; + while ((run = MAIN_THREAD_QUEUE.poll()) != null) { + run.run(); + hadLoad = true; ++ if (pendingRequest != null && pendingRequest.isDone()) { ++ break; ++ } + } + return hadLoad; + } @@ -1191,35 +1220,36 @@ index 000000000..c334462f2 + // Obtain a PendingChunk + final PendingChunk pending; + final boolean isBlockingMain = consumer == null && server.isMainThread(); ++ final Priority taskPriority = calculatePriority(isBlockingMain, priority); + synchronized (pendingChunks) { + PendingChunk pendingChunk = pendingChunks.get(key); + if (pendingChunk == null) { -+ pending = new PendingChunk(x, z, key, gen, calculatePriority(isBlockingMain, priority)); ++ 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, calculatePriority(isBlockingMain, priority)); ++ pending = new PendingChunk(x, z, key, true, taskPriority); + pendingChunks.put(key, pending); + } else { + pending = pendingChunk; -+ -+ Priority newPriority = calculatePriority(isBlockingMain, priority); -+ if (pending.taskPriority != newPriority) { -+ pending.bumpPriority(newPriority); ++ if (pending.taskPriority != taskPriority) { ++ pending.bumpPriority(taskPriority); + } + } + } ++ + // Listen for when result is ready + final CompletableFuture future = new CompletableFuture<>(); + PendingChunkRequest request = pending.addListener(future, gen); -+ if (IS_CHUNK_THREAD.get()) { -+ pending.loadTask.run(); ++ if (taskPriority != Priority.URGENT && Thread.currentThread() instanceof ExecutorThread) { ++ PriorityQueuedExecutor executor = ((ExecutorThread) Thread.currentThread()).getExecutor(); ++ Runnable run; ++ while ((run = executor.getUrgentTask()) != null) { ++ run.run(); ++ } + } -+ -+ if (isBlockingMain && pending.hasFinished) { -+ processChunkLoads(); -+ request.initialReturnChunk = pending.postChunk(); -+ return request; ++ if (isBlockingMain || isChunkThread()) { ++ pending.loadTask.run(); + } + + if (isBlockingMain) { @@ -1228,7 +1258,7 @@ index 000000000..c334462f2 + // We aren't done, obtain lock on queue + synchronized (MAIN_THREAD_QUEUE) { + // We may of received our request now, check it -+ if (processChunkLoads()) { ++ if (processChunkLoads(future)) { + // If we processed SOMETHING, don't wait + continue; + } @@ -1239,7 +1269,7 @@ index 000000000..c334462f2 + } + } + // Queue has been notified or timed out, process it -+ processChunkLoads(); ++ processChunkLoads(future); + } + // We should be done AND posted into chunk map now, return it + request.initialReturnChunk = future.join(); @@ -1350,6 +1380,17 @@ index 000000000..c334462f2 + } + } + ++ 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; @@ -1402,11 +1443,6 @@ index 000000000..c334462f2 + } + } + -+ private Chunk generateChunkExecutor() { -+ IS_CHUNK_THREAD.set(true); -+ IS_CHUNK_GEN_THREAD.set(true); -+ return generateChunk(); -+ } + private Chunk generateChunk() { + synchronized (this) { + if (requests.get() <= 0) { @@ -1497,7 +1533,11 @@ index 000000000..c334462f2 + // 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) { -+ MAIN_THREAD_QUEUE.add(this::postChunk); ++ if (this.taskPriority == Priority.URGENT) { ++ MAIN_THREAD_QUEUE.addFirst(this::postChunk); ++ } else { ++ MAIN_THREAD_QUEUE.addLast(this::postChunk); ++ } + MAIN_THREAD_QUEUE.notify(); + } + } @@ -1564,24 +1604,18 @@ index 000000000..c334462f2 + 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. -+ if (shouldGenSync) { -+ genTask = generationExecutor.createPendingTask(this::generateChunk, taskPriority); -+ } else { -+ genTask = generationExecutor.createPendingTask(this::generateChunkExecutor, taskPriority); -+ } ++ genTask = generationExecutor.createPendingTask(this::generateChunk, taskPriority); + loadTask = EXECUTOR.createPendingTask(this, taskPriority); -+ if (!IS_CHUNK_THREAD.get()) { ++ if (!isChunkThread()) { + // We will execute it outside of the synchronized context immediately after -+ EXECUTOR.submitTask(loadTask); ++ loadTask.submit(); + } + } + return new PendingChunkRequest(this, gen); + } + -+ + @Override + public void run() { -+ IS_CHUNK_THREAD.set(true); + try { + if (!loadFinished(loadChunk(x, z))) { + return; @@ -1597,18 +1631,23 @@ index 000000000..c334462f2 + if (shouldGenSync) { + synchronized (this) { + setStatus(PendingStatus.GENERATION_PENDING); -+ MAIN_THREAD_QUEUE.add(() -> generateFinished(this.generateChunk())); ++ 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 (IS_CHUNK_GEN_THREAD.get()) { ++ 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 { -+ generationExecutor.submitTask(genTask); ++ genTask.submit(); + } + } + } @@ -1618,6 +1657,10 @@ index 000000000..c334462f2 + } + + void bumpPriority(Priority newPriority) { ++ if (taskPriority.ordinal() >= newPriority.ordinal()) { ++ return; ++ } ++ + this.taskPriority = newPriority; + PriorityQueuedExecutor.PendingTask loadTask = this.loadTask; + PriorityQueuedExecutor.PendingTask genTask = this.genTask; @@ -1655,7 +1698,7 @@ index 000000000..c334462f2 + +} diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java -index 2c7c8adf7..62c524ef3 100644 +index 2c7c8adf7c..62c524ef35 100644 --- a/src/main/java/net/minecraft/server/PlayerChunk.java +++ b/src/main/java/net/minecraft/server/PlayerChunk.java @@ -29,16 +29,59 @@ public class PlayerChunk { @@ -1757,7 +1800,7 @@ index 2c7c8adf7..62c524ef3 100644 } } diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java -index 95baa1dc8..9f6028586 100644 +index 95baa1dc8b..9f60285868 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java @@ -27,10 +27,10 @@ public class PlayerChunkMap { @@ -1817,7 +1860,7 @@ index 95baa1dc8..9f6028586 100644 private void e() { diff --git a/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java b/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java -index 9c34319b6..7149b1472 100644 +index 9c34319b6e..7149b1472b 100644 --- a/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java +++ b/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java @@ -35,7 +35,7 @@ public class RegionLimitedWorldAccess implements GeneratorAccess { @@ -1830,7 +1873,7 @@ index 9c34319b6..7149b1472 100644 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 d868149d1..0d45d933e 100644 +index d868149d1a..0d45d933ee 100644 --- a/src/main/java/net/minecraft/server/SchedulerBatch.java +++ b/src/main/java/net/minecraft/server/SchedulerBatch.java @@ -9,6 +9,7 @@ public class SchedulerBatch, R> { @@ -1885,7 +1928,7 @@ index d868149d1..0d45d933e 100644 } } diff --git a/src/main/java/net/minecraft/server/StructurePiece.java b/src/main/java/net/minecraft/server/StructurePiece.java -index a5cf017da..def8730b8 100644 +index a5cf017da1..def8730b86 100644 --- a/src/main/java/net/minecraft/server/StructurePiece.java +++ b/src/main/java/net/minecraft/server/StructurePiece.java @@ -14,7 +14,7 @@ public abstract class StructurePiece { @@ -1912,7 +1955,7 @@ index a5cf017da..def8730b8 100644 return null; } diff --git a/src/main/java/net/minecraft/server/StructureStart.java b/src/main/java/net/minecraft/server/StructureStart.java -index 1926c902a..1117e4ae2 100644 +index 1926c902ad..1117e4ae27 100644 --- a/src/main/java/net/minecraft/server/StructureStart.java +++ b/src/main/java/net/minecraft/server/StructureStart.java @@ -6,7 +6,7 @@ import java.util.List; @@ -1961,7 +2004,7 @@ index 1926c902a..1117e4ae2 100644 } diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java -index 016d50d3c..f1495d30c 100644 +index 016d50d3cb..f1495d30cb 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -46,7 +46,7 @@ import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; @@ -2058,7 +2101,7 @@ index 016d50d3c..f1495d30c 100644 if (entity == null) return false; if (entity.valid) { MinecraftServer.LOGGER.error("Attempted Double World add on " + entity, new Throwable()); return true; } // Paper diff --git a/src/main/java/net/minecraft/server/WorldGenStronghold.java b/src/main/java/net/minecraft/server/WorldGenStronghold.java -index fa99fe014..4f49786aa 100644 +index fa99fe0146..4f49786aa3 100644 --- a/src/main/java/net/minecraft/server/WorldGenStronghold.java +++ b/src/main/java/net/minecraft/server/WorldGenStronghold.java @@ -9,24 +9,29 @@ import java.util.Random; @@ -2205,7 +2248,7 @@ index fa99fe014..4f49786aa 100644 } } diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java -index 6e54b71e8..a54ea5a69 100644 +index 6e54b71e88..a54ea5a69c 100644 --- a/src/main/java/net/minecraft/server/WorldServer.java +++ b/src/main/java/net/minecraft/server/WorldServer.java @@ -731,7 +731,7 @@ public class WorldServer extends World implements IAsyncTaskHandler { @@ -2218,7 +2261,7 @@ index 6e54b71e8..a54ea5a69 100644 } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 6d72db7bd..32bf4e589 100644 +index 6d72db7bd3..32bf4e589c 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -1014,8 +1014,12 @@ public final class CraftServer implements Server { @@ -2246,7 +2289,7 @@ index 6d72db7bd..32bf4e589 100644 } } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 9c266b450..f3a9649ef 100644 +index 9c266b4502..f3a9649ef6 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -162,6 +162,16 @@ public class CraftWorld implements World { @@ -2284,7 +2327,7 @@ index 9c266b450..f3a9649ef 100644 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 439bf2c24..12e6a4ea3 100644 +index 439bf2c247..12e6a4ea35 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -78,6 +78,7 @@ public class CraftEventFactory { @@ -2346,7 +2389,7 @@ index 439bf2c24..12e6a4ea3 100644 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 9c2adb235..62c197b80 100644 +index 9c2adb2351..62c197b80d 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