diff --git a/patches/server/0005-Paper-config-files.patch b/patches/server/0005-Paper-config-files.patch
index 553c918875..3303f32c8a 100644
--- a/patches/server/0005-Paper-config-files.patch
+++ b/patches/server/0005-Paper-config-files.patch
@@ -487,10 +487,10 @@ index 0000000000000000000000000000000000000000..d9502ba028a96f9cc846f9ed428bd806
+}
diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
new file mode 100644
-index 0000000000000000000000000000000000000000..b92bfd89e32becde2e7630c6116c16f8a4f6614a
+index 0000000000000000000000000000000000000000..7e88b1fc1ff700a7771b38f139f4472eaeaf8714
--- /dev/null
+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
-@@ -0,0 +1,331 @@
+@@ -0,0 +1,355 @@
+package io.papermc.paper.configuration;
+
+import co.aikar.timings.MinecraftTimings;
@@ -523,6 +523,45 @@ index 0000000000000000000000000000000000000000..b92bfd89e32becde2e7630c6116c16f8
+ public static GlobalConfiguration get() {
+ return instance;
+ }
++
++ public ChunkLoadingBasic chunkLoadingBasic;
++
++ public class ChunkLoadingBasic extends ConfigurationPart {
++ @Comment("The maximum rate in chunks per second that the server will send to any individual player. Set to -1 to disable this limit.")
++ public double playerMaxChunkSendRate = 75.0;
++
++ @Comment(
++ "The maximum rate at which chunks will load for any individual player. " +
++ "Note that this setting also affects chunk generations, since a chunk load is always first issued to test if a" +
++ "chunk is already generated. Set to -1 to disable this limit."
++ )
++ public double playerMaxChunkLoadRate = 100.0;
++
++ @Comment("The maximum rate at which chunks will generate for any individual player. Set to -1 to disable this limit.")
++ public double playerMaxChunkGenerateRate = -1.0;
++ }
++
++ public ChunkLoadingAdvanced chunkLoadingAdvanced;
++
++ public class ChunkLoadingAdvanced extends ConfigurationPart {
++ @Comment(
++ "Set to true if the server will match the chunk send radius that clients have configured" +
++ "in their view distance settings if the client is less-than the server's send distance."
++ )
++ public boolean autoConfigSendDistance = true;
++
++ @Comment(
++ "Specifies the maximum amount of concurrent chunk loads that an individual player can have." +
++ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit."
++ )
++ public int playerMaxConcurrentChunkLoads = 0;
++
++ @Comment(
++ "Specifies the maximum amount of concurrent chunk generations that an individual player can have." +
++ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit."
++ )
++ public int playerMaxConcurrentChunkGenerates = 0;
++ }
+ static void set(GlobalConfiguration instance) {
+ GlobalConfiguration.instance = instance;
+ }
@@ -638,21 +677,6 @@ index 0000000000000000000000000000000000000000..b92bfd89e32becde2e7630c6116c16f8
+ public int incomingPacketThreshold = 300;
+ }
+
-+ public ChunkLoading chunkLoading;
-+
-+ public class ChunkLoading extends ConfigurationPart {
-+ public int minLoadRadius = 2;
-+ public int maxConcurrentSends = 2;
-+ public boolean autoconfigSendDistance = true;
-+ public double targetPlayerChunkSendRate = 100.0;
-+ public double globalMaxChunkSendRate = -1.0;
-+ public boolean enableFrustumPriority = false;
-+ public double globalMaxChunkLoadRate = -1.0;
-+ public double playerMaxConcurrentLoads = 20.0;
-+ public double globalMaxConcurrentLoads = 500.0;
-+ public double playerMaxChunkLoadRate = -1.0;
-+ }
-+
+ public UnsupportedSettings unsupportedSettings;
+
+ public class UnsupportedSettings extends ConfigurationPart {
@@ -711,7 +735,7 @@ index 0000000000000000000000000000000000000000..b92bfd89e32becde2e7630c6116c16f8
+
+ @PostProcess
+ private void postProcess() {
-+ //io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.init(this);
++
+ }
+ }
+
diff --git a/patches/server/0007-ConcurrentUtil.patch b/patches/server/0007-ConcurrentUtil.patch
index 44a4f07316..b285b3c6e3 100644
--- a/patches/server/0007-ConcurrentUtil.patch
+++ b/patches/server/0007-ConcurrentUtil.patch
@@ -1431,167 +1431,12 @@ index 0000000000000000000000000000000000000000..f84a622dc29750139ac280f480b7cd13
+ }
+ }
+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java
+diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java
new file mode 100644
-index 0000000000000000000000000000000000000000..094eff418b4e3bffce020d650931b4d9e58fa9ed
+index 0000000000000000000000000000000000000000..6bad6f8ecc0944d2f406924c7de7e227ff1e70fa
--- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java
-@@ -0,0 +1,149 @@
-+package ca.spottedleaf.concurrentutil.collection;
-+
-+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
-+import ca.spottedleaf.concurrentutil.util.Validate;
-+
-+import java.lang.invoke.VarHandle;
-+import java.util.ConcurrentModificationException;
-+
-+/**
-+ * Single reader thread single writer thread queue. The reader side of the queue is ordered by acquire semantics,
-+ * and the writer side of the queue is ordered by release semantics.
-+ */
-+// TODO test
-+public class SRSWLinkedQueue {
-+
-+ // always non-null
-+ protected LinkedNode head;
-+
-+ // always non-null
-+ protected LinkedNode tail;
-+
-+ /* IMPL NOTE: Leave hashCode and equals to their defaults */
-+
-+ public SRSWLinkedQueue() {
-+ final LinkedNode dummy = new LinkedNode<>(null, null);
-+ this.head = this.tail = dummy;
-+ }
-+
-+ /**
-+ * Must be the reader thread.
-+ *
-+ *
-+ * Returns, without removing, the first element of this queue.
-+ *
-+ * @return Returns, without removing, the first element of this queue.
-+ */
-+ public E peekFirst() {
-+ LinkedNode head = this.head;
-+ E ret = head.getElementPlain();
-+ if (ret == null) {
-+ head = head.getNextAcquire();
-+ if (head == null) {
-+ // empty
-+ return null;
-+ }
-+ // update head reference for next poll() call
-+ this.head = head;
-+ // guaranteed to be non-null
-+ ret = head.getElementPlain();
-+ if (ret == null) {
-+ throw new ConcurrentModificationException("Multiple reader threads");
-+ }
-+ }
-+
-+ return ret;
-+ }
-+
-+ /**
-+ * Must be the reader thread.
-+ *
-+ *
-+ * Returns and removes the first element of this queue.
-+ *
-+ * @return Returns and removes the first element of this queue.
-+ */
-+ public E poll() {
-+ LinkedNode head = this.head;
-+ E ret = head.getElementPlain();
-+ if (ret == null) {
-+ head = head.getNextAcquire();
-+ if (head == null) {
-+ // empty
-+ return null;
-+ }
-+ // guaranteed to be non-null
-+ ret = head.getElementPlain();
-+ if (ret == null) {
-+ throw new ConcurrentModificationException("Multiple reader threads");
-+ }
-+ }
-+
-+ head.setElementPlain(null);
-+ LinkedNode next = head.getNextAcquire();
-+ this.head = next == null ? head : next;
-+
-+ return ret;
-+ }
-+
-+ /**
-+ * Must be the writer thread.
-+ *
-+ *
-+ * Adds the element to the end of the queue.
-+ *
-+ *
-+ * @throws NullPointerException If the provided element is null
-+ */
-+ public void addLast(final E element) {
-+ Validate.notNull(element, "Provided element cannot be null");
-+ final LinkedNode append = new LinkedNode<>(element, null);
-+
-+ this.tail.setNextRelease(append);
-+ this.tail = append;
-+ }
-+
-+ protected static final class LinkedNode {
-+
-+ protected volatile Object element;
-+ protected volatile LinkedNode next;
-+
-+ protected static final VarHandle ELEMENT_HANDLE = ConcurrentUtil.getVarHandle(LinkedNode.class, "element", Object.class);
-+ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(LinkedNode.class, "next", LinkedNode.class);
-+
-+ protected LinkedNode(final Object element, final LinkedNode next) {
-+ ELEMENT_HANDLE.set(this, element);
-+ NEXT_HANDLE.set(this, next);
-+ }
-+
-+ /* element */
-+
-+ @SuppressWarnings("unchecked")
-+ protected final E getElementPlain() {
-+ return (E)ELEMENT_HANDLE.get(this);
-+ }
-+
-+ protected final void setElementPlain(final E update) {
-+ ELEMENT_HANDLE.set(this, (Object)update);
-+ }
-+ /* next */
-+
-+ @SuppressWarnings("unchecked")
-+ protected final LinkedNode getNextPlain() {
-+ return (LinkedNode)NEXT_HANDLE.get(this);
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ protected final LinkedNode getNextAcquire() {
-+ return (LinkedNode)NEXT_HANDLE.getAcquire(this);
-+ }
-+
-+ protected final void setNextPlain(final LinkedNode next) {
-+ NEXT_HANDLE.set(this, next);
-+ }
-+
-+ protected final void setNextRelease(final LinkedNode next) {
-+ NEXT_HANDLE.setRelease(this, next);
-+ }
-+ }
-+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..46d1bd01542ebeeffc0006a5c585a50dbbbff907
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java
-@@ -0,0 +1,112 @@
++++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java
+@@ -0,0 +1,110 @@
+package ca.spottedleaf.concurrentutil.completable;
+
+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
@@ -1601,9 +1446,9 @@ index 0000000000000000000000000000000000000000..46d1bd01542ebeeffc0006a5c585a50d
+import org.slf4j.LoggerFactory;
+import java.util.function.BiConsumer;
+
-+public final class Completable {
++public final class CallbackCompletable {
+
-+ private static final Logger LOGGER = LoggerFactory.getLogger(Completable.class);
++ private static final Logger LOGGER = LoggerFactory.getLogger(CallbackCompletable.class);
+
+ private final MultiThreadedQueue> waiters = new MultiThreadedQueue<>();
+ private T result;
@@ -1655,8 +1500,6 @@ index 0000000000000000000000000000000000000000..46d1bd01542ebeeffc0006a5c585a50d
+ private void completeWaiter(final BiConsumer consumer, final T result, final Throwable throwable) {
+ try {
+ consumer.accept(result, throwable);
-+ } catch (final ThreadDeath death) {
-+ throw death;
+ } catch (final Throwable throwable2) {
+ LOGGER.error("Failed to complete callback " + ConcurrentUtil.genericToString(consumer), throwable2);
+ }
@@ -1700,222 +1543,752 @@ index 0000000000000000000000000000000000000000..46d1bd01542ebeeffc0006a5c585a50d
+
+ @Override
+ public boolean cancel() {
-+ return Completable.this.waiters.remove(this.waiter);
++ return CallbackCompletable.this.waiters.remove(this.waiter);
+ }
+ }
+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java
+\ No newline at end of file
+diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java
new file mode 100644
-index 0000000000000000000000000000000000000000..18d646676fd022afd64afaac30ec1bd283a73b0e
+index 0000000000000000000000000000000000000000..365616439fa079017d648ed7f6ddf6950a691adf
--- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java
-@@ -0,0 +1,208 @@
-+package ca.spottedleaf.concurrentutil.executor;
++++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java
+@@ -0,0 +1,737 @@
++package ca.spottedleaf.concurrentutil.completable;
+
+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
-+import java.util.function.BooleanSupplier;
++import ca.spottedleaf.concurrentutil.util.Validate;
++import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
++import java.lang.invoke.VarHandle;
++import java.util.concurrent.CompletableFuture;
++import java.util.concurrent.CompletionException;
++import java.util.concurrent.CompletionStage;
++import java.util.concurrent.Executor;
++import java.util.concurrent.ForkJoinPool;
++import java.util.concurrent.locks.LockSupport;
++import java.util.function.BiConsumer;
++import java.util.function.BiFunction;
++import java.util.function.Consumer;
++import java.util.function.Function;
++import java.util.function.Supplier;
+
-+/**
-+ * Base implementation for an abstract queue of tasks which are executed either synchronously or asynchronously.
-+ *
-+ *
-+ * The implementation supports tracking task executions using {@link #getTotalTasksScheduled()} and
-+ * {@link #getTotalTasksExecuted()}, and optionally shutting down the executor using {@link #shutdown()}
-+ *
-+ *
-+ *
-+ * The base implementation does not provide a method to queue a task for execution, rather that is specified in
-+ * the specific implementation. However, it is required that a specific implementation provides a method to
-+ * queue a task or create a task. A queued task is one which will eventually be executed,
-+ * and a created task must be queued to execute via {@link BaseTask#queue()} or be executed manually via
-+ * {@link BaseTask#execute()}. This choice of delaying the queueing of a task may be useful to provide a task handle
-+ * which may be cancelled or adjusted before the actual real task logic is ready to be executed.
-+ *
-+ */
-+public interface BaseExecutor {
++public final class Completable {
+
-+ /**
-+ * Returns whether every task scheduled to this queue has been removed and executed or cancelled. If no tasks have been queued,
-+ * returns {@code true}.
-+ *
-+ * @return {@code true} if all tasks that have been queued have finished executing or no tasks have been queued, {@code false} otherwise.
-+ */
-+ public default boolean haveAllTasksExecuted() {
-+ // order is important
-+ // if new tasks are scheduled between the reading of these variables, scheduled is guaranteed to be higher -
-+ // so our check fails, and we try again
-+ final long completed = this.getTotalTasksExecuted();
-+ final long scheduled = this.getTotalTasksScheduled();
++ private static final Logger LOGGER = LoggerFactory.getLogger(Completable.class);
++ private static final Function super Throwable, ? extends Throwable> DEFAULT_EXCEPTION_HANDLER = (final Throwable thr) -> {
++ LOGGER.error("Unhandled exception during Completable operation", thr);
++ return thr;
++ };
+
-+ return completed == scheduled;
++ public static Executor getDefaultExecutor() {
++ return ForkJoinPool.commonPool();
+ }
+
-+ /**
-+ * Returns the number of tasks that have been scheduled or execute or are pending to be scheduled.
-+ */
-+ public long getTotalTasksScheduled();
++ private static final Transform, ?> COMPLETED_STACK = new Transform<>(null, null, null, null) {
++ @Override
++ public void run() {}
++ };
++ private volatile Transform, T> completeStack;
++ private static final VarHandle COMPLETE_STACK_HANDLE = ConcurrentUtil.getVarHandle(Completable.class, "completeStack", Transform.class);
+
-+ /**
-+ * Returns the number of tasks that have fully been executed.
-+ */
-+ public long getTotalTasksExecuted();
++ private static final Object NULL_MASK = new Object();
++ private volatile Object result;
++ private static final VarHandle RESULT_HANDLE = ConcurrentUtil.getVarHandle(Completable.class, "result", Object.class);
+
-+ /**
-+ * Waits until this queue has had all of its tasks executed (NOT removed). See {@link #haveAllTasksExecuted()}
-+ *
-+ * This call is most effective after a {@link #shutdown()} call, as the shutdown call guarantees no tasks can
-+ * be executed and the waitUntilAllExecuted call makes sure the queue is empty. Effectively, using shutdown then using
-+ * waitUntilAllExecuted ensures this queue is empty - and most importantly, will remain empty.
-+ *
-+ *
-+ * This method is not guaranteed to be immediately responsive to queue state, so calls may take significantly more
-+ * time than expected. Effectively, do not rely on this call being fast - even if there are few tasks scheduled.
-+ *
-+ *
-+ * Note: Interruptions to the the current thread have no effect. Interrupt status is also not affected by this call.
-+ *
-+ *
-+ * @throws IllegalStateException If the current thread is not allowed to wait
-+ */
-+ public default void waitUntilAllExecuted() throws IllegalStateException {
-+ long failures = 1L; // start at 0.25ms
++ private Object getResultPlain() {
++ return (Object)RESULT_HANDLE.get(this);
++ }
+
-+ while (!this.haveAllTasksExecuted()) {
-+ Thread.yield();
-+ failures = ConcurrentUtil.linearLongBackoff(failures, 250_000L, 5_000_000L); // 500us, 5ms
++ private Object getResultVolatile() {
++ return (Object)RESULT_HANDLE.getVolatile(this);
++ }
++
++ private void pushStackOrRun(final Transform, T> push) {
++ int failures = 0;
++ for (Transform, T> curr = (Transform, T>)COMPLETE_STACK_HANDLE.getVolatile(this);;) {
++ if (curr == COMPLETED_STACK) {
++ push.execute();
++ return;
++ }
++
++ push.next = curr;
++
++ for (int i = 0; i < failures; ++i) {
++ ConcurrentUtil.backoff();
++ }
++
++ if (curr == (curr = (Transform, T>)COMPLETE_STACK_HANDLE.compareAndExchange(this, curr, push))) {
++ return;
++ }
++ push.next = null;
++ ++failures;
+ }
+ }
+
-+ /**
-+ * Executes the next available task.
-+ *
-+ * @return {@code true} if a task was executed, {@code false} otherwise
-+ * @throws IllegalStateException If the current thread is not allowed to execute a task
-+ */
-+ public boolean executeTask() throws IllegalStateException;
++ private void propagateStack() {
++ Transform, T> topStack = (Transform, T>)COMPLETE_STACK_HANDLE.getAndSet(this, COMPLETED_STACK);
++ while (topStack != null) {
++ topStack.execute();
++ topStack = topStack.next;
++ }
++ }
+
-+ /**
-+ * Executes all queued tasks.
-+ *
-+ * @return {@code true} if a task was executed, {@code false} otherwise
-+ * @throws IllegalStateException If the current thread is not allowed to execute a task
-+ */
-+ public default boolean executeAll() {
-+ if (!this.executeTask()) {
++ private static Object maskNull(final Object res) {
++ return res == null ? NULL_MASK : res;
++ }
++
++ private static Object unmaskNull(final Object res) {
++ return res == NULL_MASK ? null : res;
++ }
++
++ private static Executor checkExecutor(final Executor executor) {
++ return Validate.notNull(executor, "Executor may not be null");
++ }
++
++ public Completable() {}
++
++ private Completable(final Object complete) {
++ COMPLETE_STACK_HANDLE.set(this, COMPLETED_STACK);
++ RESULT_HANDLE.setRelease(this, complete);
++ }
++
++ public static Completable completed(final T value) {
++ return new Completable<>(maskNull(value));
++ }
++
++ public static Completable failed(final Throwable ex) {
++ Validate.notNull(ex, "Exception may not be null");
++
++ return new Completable<>(new ExceptionResult(ex));
++ }
++
++ public static Completable supplied(final Supplier supplier) {
++ return supplied(supplier, DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public static Completable supplied(final Supplier supplier, final Function super Throwable, ? extends Throwable> exceptionHandler) {
++ try {
++ return completed(supplier.get());
++ } catch (final Throwable throwable) {
++ Throwable complete;
++ try {
++ complete = exceptionHandler.apply(throwable);
++ } catch (final Throwable thr2) {
++ throwable.addSuppressed(thr2);
++ complete = throwable;
++ }
++ return failed(complete);
++ }
++ }
++
++ public static Completable suppliedAsync(final Supplier supplier, final Executor executor) {
++ return suppliedAsync(supplier, executor, DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public static Completable suppliedAsync(final Supplier supplier, final Executor executor, final Function super Throwable, ? extends Throwable> exceptionHandler) {
++ final Completable ret = new Completable<>();
++
++ class AsyncSuppliedCompletable implements Runnable, CompletableFuture.AsynchronousCompletionTask {
++ @Override
++ public void run() {
++ try {
++ ret.complete(supplier.get());
++ } catch (final Throwable throwable) {
++ Throwable complete;
++ try {
++ complete = exceptionHandler.apply(throwable);
++ } catch (final Throwable thr2) {
++ throwable.addSuppressed(thr2);
++ complete = throwable;
++ }
++ ret.completeExceptionally(complete);
++ }
++ }
++ }
++
++ try {
++ executor.execute(new AsyncSuppliedCompletable());
++ } catch (final Throwable throwable) {
++ Throwable complete;
++ try {
++ complete = exceptionHandler.apply(throwable);
++ } catch (final Throwable thr2) {
++ throwable.addSuppressed(thr2);
++ complete = throwable;
++ }
++ ret.completeExceptionally(complete);
++ }
++
++ return ret;
++ }
++
++ private boolean completeRaw(final Object value) {
++ if ((Object)RESULT_HANDLE.getVolatile(this) != null || !(boolean)RESULT_HANDLE.compareAndSet(this, (Object)null, value)) {
+ return false;
+ }
+
-+ while (this.executeTask());
-+
++ this.propagateStack();
+ return true;
+ }
+
-+ /**
-+ * Waits and executes tasks until the condition returns {@code true}.
-+ *
-+ * WARNING: This function is not suitable for waiting until a deadline!
-+ * Use {@link #executeUntil(long)} or {@link #executeConditionally(BooleanSupplier, long)} instead.
-+ *
-+ */
-+ public default void executeConditionally(final BooleanSupplier condition) {
-+ long failures = 0;
-+ while (!condition.getAsBoolean()) {
-+ if (this.executeTask()) {
-+ failures = failures >>> 2;
-+ } else {
-+ failures = ConcurrentUtil.linearLongBackoff(failures, 100_000L, 10_000_000L); // 100us, 10ms
++ public boolean complete(final T result) {
++ return this.completeRaw(maskNull(result));
++ }
++
++ public boolean completeExceptionally(final Throwable exception) {
++ Validate.notNull(exception, "Exception may not be null");
++
++ return this.completeRaw(new ExceptionResult(exception));
++ }
++
++ public boolean isDone() {
++ return this.getResultVolatile() != null;
++ }
++
++ public boolean isNormallyComplete() {
++ return this.getResultVolatile() != null && !(this.getResultVolatile() instanceof ExceptionResult);
++ }
++
++ public boolean isExceptionallyComplete() {
++ return this.getResultVolatile() instanceof ExceptionResult;
++ }
++
++ public Throwable getException() {
++ final Object res = this.getResultVolatile();
++ if (res == null) {
++ return null;
++ }
++
++ if (!(res instanceof ExceptionResult exRes)) {
++ throw new IllegalStateException("Not completed exceptionally");
++ }
++
++ return exRes.ex;
++ }
++
++ public T getNow(final T dfl) throws CompletionException {
++ final Object res = this.getResultVolatile();
++ if (res == null) {
++ return dfl;
++ }
++
++ if (res instanceof ExceptionResult exRes) {
++ throw new CompletionException(exRes.ex);
++ }
++
++ return (T)unmaskNull(res);
++ }
++
++ public T join() throws CompletionException {
++ if (this.isDone()) {
++ return this.getNow(null);
++ }
++
++ final UnparkTransform unparkTransform = new UnparkTransform<>(this, Thread.currentThread());
++
++ this.pushStackOrRun(unparkTransform);
++
++ boolean interuptted = false;
++ while (!unparkTransform.isReleasable()) {
++ try {
++ ForkJoinPool.managedBlock(unparkTransform);
++ } catch (final InterruptedException ex) {
++ interuptted = true;
+ }
+ }
++
++ if (interuptted) {
++ Thread.currentThread().interrupt();
++ }
++
++ return this.getNow(null);
++ }
++
++ public CompletableFuture toFuture() {
++ final Object rawResult = this.getResultVolatile();
++ if (rawResult != null) {
++ if (rawResult instanceof ExceptionResult exRes) {
++ return CompletableFuture.failedFuture(exRes.ex);
++ } else {
++ return CompletableFuture.completedFuture((T)unmaskNull(rawResult));
++ }
++ }
++
++ final CompletableFuture ret = new CompletableFuture<>();
++
++ class ToFuture implements BiConsumer {
++
++ @Override
++ public void accept(final T res, final Throwable ex) {
++ if (ex != null) {
++ ret.completeExceptionally(ex);
++ } else {
++ ret.complete(res);
++ }
++ }
++ }
++
++ this.whenComplete(new ToFuture());
++
++ return ret;
++ }
++
++ public static Completable fromFuture(final CompletionStage stage) {
++ final Completable ret = new Completable<>();
++
++ class FromFuture implements BiConsumer {
++ @Override
++ public void accept(final T res, final Throwable ex) {
++ if (ex != null) {
++ ret.completeExceptionally(ex);
++ } else {
++ ret.complete(res);
++ }
++ }
++ }
++
++ stage.whenComplete(new FromFuture());
++
++ return ret;
++ }
++
++
++ public Completable thenApply(final Function super T, ? extends U> function) {
++ return this.thenApply(function, DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable thenApply(final Function super T, ? extends U> function, final Function super Throwable, ? extends Throwable> exceptionHandler) {
++ Validate.notNull(function, "Function may not be null");
++ Validate.notNull(exceptionHandler, "Exception handler may not be null");
++
++ final Completable ret = new Completable<>();
++ this.pushStackOrRun(new ApplyTransform<>(null, this, ret, exceptionHandler, function));
++ return ret;
++ }
++
++ public Completable thenApplyAsync(final Function super T, ? extends U> function) {
++ return this.thenApplyAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable thenApplyAsync(final Function super T, ? extends U> function, final Executor executor) {
++ return this.thenApplyAsync(function, executor, DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable thenApplyAsync(final Function super T, ? extends U> function, final Executor executor, final Function super Throwable, ? extends Throwable> exceptionHandler) {
++ Validate.notNull(function, "Function may not be null");
++ Validate.notNull(exceptionHandler, "Exception handler may not be null");
++
++ final Completable ret = new Completable<>();
++ this.pushStackOrRun(new ApplyTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function));
++ return ret;
++ }
++
++
++ public Completable thenAccept(final Consumer super T> consumer) {
++ return this.thenAccept(consumer, DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable thenAccept(final Consumer super T> consumer, final Function super Throwable, ? extends Throwable> exceptionHandler) {
++ Validate.notNull(consumer, "Consumer may not be null");
++ Validate.notNull(exceptionHandler, "Exception handler may not be null");
++
++ final Completable ret = new Completable<>();
++ this.pushStackOrRun(new AcceptTransform<>(null, this, ret, exceptionHandler, consumer));
++ return ret;
++ }
++
++ public Completable thenAcceptAsync(final Consumer super T> consumer) {
++ return this.thenAcceptAsync(consumer, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable thenAcceptAsync(final Consumer super T> consumer, final Executor executor) {
++ return this.thenAcceptAsync(consumer, executor, DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable thenAcceptAsync(final Consumer super T> consumer, final Executor executor, final Function super Throwable, ? extends Throwable> exceptionHandler) {
++ Validate.notNull(consumer, "Consumer may not be null");
++ Validate.notNull(exceptionHandler, "Exception handler may not be null");
++
++ final Completable ret = new Completable<>();
++ this.pushStackOrRun(new AcceptTransform<>(checkExecutor(executor), this, ret, exceptionHandler, consumer));
++ return ret;
++ }
++
++
++ public Completable thenRun(final Runnable run) {
++ return this.thenRun(run, DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable thenRun(final Runnable run, final Function super Throwable, ? extends Throwable> exceptionHandler) {
++ Validate.notNull(run, "Run may not be null");
++ Validate.notNull(exceptionHandler, "Exception handler may not be null");
++
++ final Completable ret = new Completable<>();
++ this.pushStackOrRun(new RunTransform<>(null, this, ret, exceptionHandler, run));
++ return ret;
++ }
++
++ public Completable thenRunAsync(final Runnable run) {
++ return this.thenRunAsync(run, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable thenRunAsync(final Runnable run, final Executor executor) {
++ return this.thenRunAsync(run, executor, DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable thenRunAsync(final Runnable run, final Executor executor, final Function super Throwable, ? extends Throwable> exceptionHandler) {
++ Validate.notNull(run, "Run may not be null");
++ Validate.notNull(exceptionHandler, "Exception handler may not be null");
++
++ final Completable ret = new Completable<>();
++ this.pushStackOrRun(new RunTransform<>(checkExecutor(executor), this, ret, exceptionHandler, run));
++ return ret;
++ }
++
++
++ public Completable handle(final BiFunction super T, ? super Throwable, ? extends U> function) {
++ return this.handle(function, DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable handle(final BiFunction super T, ? super Throwable, ? extends U> function,
++ final Function super Throwable, ? extends Throwable> exceptionHandler) {
++ Validate.notNull(function, "Function may not be null");
++ Validate.notNull(exceptionHandler, "Exception handler may not be null");
++
++ final Completable ret = new Completable<>();
++ this.pushStackOrRun(new HandleTransform<>(null, this, ret, exceptionHandler, function));
++ return ret;
++ }
++
++ public Completable handleAsync(final BiFunction super T, ? super Throwable, ? extends U> function) {
++ return this.handleAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable handleAsync(final BiFunction super T, ? super Throwable, ? extends U> function,
++ final Executor executor) {
++ return this.handleAsync(function, executor, DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable handleAsync(final BiFunction super T, ? super Throwable, ? extends U> function,
++ final Executor executor,
++ final Function super Throwable, ? extends Throwable> exceptionHandler) {
++ Validate.notNull(function, "Function may not be null");
++ Validate.notNull(exceptionHandler, "Exception handler may not be null");
++
++ final Completable ret = new Completable<>();
++ this.pushStackOrRun(new HandleTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function));
++ return ret;
++ }
++
++
++ public Completable whenComplete(final BiConsumer super T, ? super Throwable> consumer) {
++ return this.whenComplete(consumer, DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable whenComplete(final BiConsumer super T, ? super Throwable> consumer, final Function super Throwable, ? extends Throwable> exceptionHandler) {
++ Validate.notNull(consumer, "Consumer may not be null");
++ Validate.notNull(exceptionHandler, "Exception handler may not be null");
++
++ final Completable ret = new Completable<>();
++ this.pushStackOrRun(new WhenTransform<>(null, this, ret, exceptionHandler, consumer));
++ return ret;
++ }
++
++ public Completable whenCompleteAsync(final BiConsumer super T, ? super Throwable> consumer) {
++ return this.whenCompleteAsync(consumer, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable whenCompleteAsync(final BiConsumer super T, ? super Throwable> consumer, final Executor executor) {
++ return this.whenCompleteAsync(consumer, executor, DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable whenCompleteAsync(final BiConsumer super T, ? super Throwable> consumer, final Executor executor,
++ final Function super Throwable, ? extends Throwable> exceptionHandler) {
++ Validate.notNull(consumer, "Consumer may not be null");
++ Validate.notNull(exceptionHandler, "Exception handler may not be null");
++
++ final Completable ret = new Completable<>();
++ this.pushStackOrRun(new WhenTransform<>(checkExecutor(executor), this, ret, exceptionHandler, consumer));
++ return ret;
++ }
++
++
++ public Completable exceptionally(final Function function) {
++ return this.exceptionally(function, DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable exceptionally(final Function function, final Function super Throwable, ? extends Throwable> exceptionHandler) {
++ Validate.notNull(function, "Function may not be null");
++ Validate.notNull(exceptionHandler, "Exception handler may not be null");
++
++ final Completable ret = new Completable<>();
++ this.pushStackOrRun(new ExceptionallyTransform<>(null, this, ret, exceptionHandler, function));
++ return ret;
++ }
++
++ public Completable exceptionallyAsync(final Function function) {
++ return this.exceptionallyAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable exceptionallyAsync(final Function function, final Executor executor) {
++ return this.exceptionallyAsync(function, executor, DEFAULT_EXCEPTION_HANDLER);
++ }
++
++ public Completable exceptionallyAsync(final Function function, final Executor executor,
++ final Function super Throwable, ? extends Throwable> exceptionHandler) {
++ Validate.notNull(function, "Function may not be null");
++ Validate.notNull(exceptionHandler, "Exception handler may not be null");
++
++ final Completable ret = new Completable<>();
++ this.pushStackOrRun(new ExceptionallyTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function));
++ return ret;
++ }
++
++ private static final class ExceptionResult {
++ public final Throwable ex;
++
++ public ExceptionResult(final Throwable ex) {
++ this.ex = ex;
++ }
+ }
+
-+ /**
-+ * Waits and executes tasks until the condition returns {@code true} or {@code System.nanoTime() - deadline >= 0}.
-+ */
-+ public default void executeConditionally(final BooleanSupplier condition, final long deadline) {
-+ long failures = 0;
-+ // double check deadline; we don't know how expensive the condition is
-+ while ((System.nanoTime() - deadline < 0L) && !condition.getAsBoolean() && (System.nanoTime() - deadline < 0L)) {
-+ if (this.executeTask()) {
-+ failures = failures >>> 2;
-+ } else {
-+ failures = ConcurrentUtil.linearLongBackoffDeadline(failures, 100_000L, 10_000_000L, deadline); // 100us, 10ms
-+ }
++ private static abstract class Transform implements Runnable, CompletableFuture.AsynchronousCompletionTask {
++
++ private Transform, T> next;
++
++ private final Executor executor;
++ protected final Completable from;
++ protected final Completable to;
++ protected final Function super Throwable, ? extends Throwable> exceptionHandler;
++
++ protected Transform(final Executor executor, final Completable from, final Completable to,
++ final Function super Throwable, ? extends Throwable> exceptionHandler) {
++ this.executor = executor;
++ this.from = from;
++ this.to = to;
++ this.exceptionHandler = exceptionHandler;
+ }
-+ }
+
-+ /**
-+ * Waits and executes tasks until {@code System.nanoTime() - deadline >= 0}.
-+ */
-+ public default void executeUntil(final long deadline) {
-+ long failures = 0;
-+ while (System.nanoTime() - deadline < 0L) {
-+ if (this.executeTask()) {
-+ failures = failures >>> 2;
-+ } else {
-+ failures = ConcurrentUtil.linearLongBackoffDeadline(failures, 100_000L, 10_000_000L, deadline); // 100us, 10ms
-+ }
-+ }
-+ }
-+
-+ /**
-+ * Prevent further additions to this queue. Attempts to add after this call has completed (potentially during) will
-+ * result in {@link IllegalStateException} being thrown.
-+ *
-+ * This operation is atomic with respect to other shutdown calls
-+ *
-+ *
-+ * After this call has completed, regardless of return value, this queue will be shutdown.
-+ *
-+ *
-+ * @return {@code true} if the queue was shutdown, {@code false} if it has shut down already
-+ * @throws UnsupportedOperationException If this queue does not support shutdown
-+ * @see #isShutdown()
-+ */
-+ public default boolean shutdown() throws UnsupportedOperationException {
-+ throw new UnsupportedOperationException();
-+ }
-+
-+ /**
-+ * Returns whether this queue has shut down. Effectively, whether new tasks will be rejected - this method
-+ * does not indicate whether all the tasks scheduled have been executed.
-+ * @return Returns whether this queue has shut down.
-+ * @see #waitUntilAllExecuted()
-+ */
-+ public default boolean isShutdown() {
-+ return false;
-+ }
-+
-+ /**
-+ * Task object returned for any {@link BaseExecutor} scheduled task.
-+ * @see BaseExecutor
-+ */
-+ public static interface BaseTask extends Cancellable {
-+
-+ /**
-+ * Causes a lazily queued task to become queued or executed
-+ *
-+ * @throws IllegalStateException If the backing queue has shutdown
-+ * @return {@code true} If the task was queued, {@code false} if the task was already queued/cancelled/executed
-+ */
-+ public boolean queue();
-+
-+ /**
-+ * Forces this task to be marked as completed.
-+ *
-+ * @return {@code true} if the task was cancelled, {@code false} if the task has already completed or is being completed.
-+ */
++ // force interface call to become virtual call
+ @Override
-+ public boolean cancel();
++ public abstract void run();
+
-+ /**
-+ * Executes this task. This will also mark the task as completing.
-+ *
-+ * Exceptions thrown from the runnable will be rethrown.
-+ *
-+ *
-+ * @return {@code true} if this task was executed, {@code false} if it was already marked as completed.
-+ */
-+ public boolean execute();
++ protected void failed(final Throwable throwable) {
++ Throwable complete;
++ try {
++ complete = this.exceptionHandler.apply(throwable);
++ } catch (final Throwable thr2) {
++ throwable.addSuppressed(thr2);
++ complete = throwable;
++ }
++ this.to.completeExceptionally(complete);
++ }
++
++ public void execute() {
++ if (this.executor == null) {
++ this.run();
++ return;
++ }
++
++ try {
++ this.executor.execute(this);
++ } catch (final Throwable throwable) {
++ this.failed(throwable);
++ }
++ }
++ }
++
++ private static final class ApplyTransform extends Transform {
++
++ private final Function super T, ? extends U> function;
++
++ public ApplyTransform(final Executor executor, final Completable from, final Completable to,
++ final Function super Throwable, ? extends Throwable> exceptionHandler,
++ final Function super T, ? extends U> function) {
++ super(executor, from, to, exceptionHandler);
++ this.function = function;
++ }
++
++ @Override
++ public void run() {
++ final Object result = this.from.getResultPlain();
++ try {
++ if (result instanceof ExceptionResult exRes) {
++ this.to.completeExceptionally(exRes.ex);
++ } else {
++ this.to.complete(this.function.apply((T)unmaskNull(result)));
++ }
++ } catch (final Throwable throwable) {
++ this.failed(throwable);
++ }
++ }
++ }
++
++ private static final class AcceptTransform extends Transform {
++ private final Consumer super T> consumer;
++
++ public AcceptTransform(final Executor executor, final Completable from, final Completable to,
++ final Function super Throwable, ? extends Throwable> exceptionHandler,
++ final Consumer super T> consumer) {
++ super(executor, from, to, exceptionHandler);
++ this.consumer = consumer;
++ }
++
++ @Override
++ public void run() {
++ final Object result = this.from.getResultPlain();
++ try {
++ if (result instanceof ExceptionResult exRes) {
++ this.to.completeExceptionally(exRes.ex);
++ } else {
++ this.consumer.accept((T)unmaskNull(result));
++ this.to.complete(null);
++ }
++ } catch (final Throwable throwable) {
++ this.failed(throwable);
++ }
++ }
++ }
++
++ private static final class RunTransform extends Transform {
++ private final Runnable run;
++
++ public RunTransform(final Executor executor, final Completable from, final Completable to,
++ final Function super Throwable, ? extends Throwable> exceptionHandler,
++ final Runnable run) {
++ super(executor, from, to, exceptionHandler);
++ this.run = run;
++ }
++
++ @Override
++ public void run() {
++ final Object result = this.from.getResultPlain();
++ try {
++ if (result instanceof ExceptionResult exRes) {
++ this.to.completeExceptionally(exRes.ex);
++ } else {
++ this.run.run();
++ this.to.complete(null);
++ }
++ } catch (final Throwable throwable) {
++ this.failed(throwable);
++ }
++ }
++ }
++
++ private static final class HandleTransform extends Transform {
++
++ private final BiFunction super T, ? super Throwable, ? extends U> function;
++
++ public HandleTransform(final Executor executor, final Completable from, final Completable to,
++ final Function super Throwable, ? extends Throwable> exceptionHandler,
++ final BiFunction super T, ? super Throwable, ? extends U> function) {
++ super(executor, from, to, exceptionHandler);
++ this.function = function;
++ }
++
++ @Override
++ public void run() {
++ final Object result = this.from.getResultPlain();
++ try {
++ if (result instanceof ExceptionResult exRes) {
++ this.to.complete(this.function.apply(null, exRes.ex));
++ } else {
++ this.to.complete(this.function.apply((T)unmaskNull(result), null));
++ }
++ } catch (final Throwable throwable) {
++ this.failed(throwable);
++ }
++ }
++ }
++
++ private static final class WhenTransform extends Transform {
++
++ private final BiConsumer super T, ? super Throwable> consumer;
++
++ public WhenTransform(final Executor executor, final Completable from, final Completable to,
++ final Function super Throwable, ? extends Throwable> exceptionHandler,
++ final BiConsumer super T, ? super Throwable> consumer) {
++ super(executor, from, to, exceptionHandler);
++ this.consumer = consumer;
++ }
++
++ @Override
++ public void run() {
++ final Object result = this.from.getResultPlain();
++ try {
++ if (result instanceof ExceptionResult exRes) {
++ this.consumer.accept(null, exRes.ex);
++ this.to.completeExceptionally(exRes.ex);
++ } else {
++ final T unmasked = (T)unmaskNull(result);
++ this.consumer.accept(unmasked, null);
++ this.to.complete(unmasked);
++ }
++ } catch (final Throwable throwable) {
++ this.failed(throwable);
++ }
++ }
++ }
++
++ private static final class ExceptionallyTransform extends Transform {
++ private final Function function;
++
++ public ExceptionallyTransform(final Executor executor, final Completable from, final Completable to,
++ final Function super Throwable, ? extends Throwable> exceptionHandler,
++ final Function function) {
++ super(executor, from, to, exceptionHandler);
++ this.function = function;
++ }
++
++ @Override
++ public void run() {
++ final Object result = this.from.getResultPlain();
++ try {
++ if (result instanceof ExceptionResult exRes) {
++ this.to.complete(this.function.apply(exRes.ex));
++ } else {
++ this.to.complete((T)unmaskNull(result));
++ }
++ } catch (final Throwable throwable) {
++ this.failed(throwable);
++ }
++ }
++ }
++
++ private static final class UnparkTransform extends Transform implements ForkJoinPool.ManagedBlocker {
++
++ private volatile Thread thread;
++
++ public UnparkTransform(final Completable from, final Thread target) {
++ super(null, from, null, null);
++ this.thread = target;
++ }
++
++ @Override
++ public void run() {
++ final Thread t = this.thread;
++ this.thread = null;
++ LockSupport.unpark(t);
++ }
++
++ @Override
++ public boolean block() throws InterruptedException {
++ while (!this.isReleasable()) {
++ if (Thread.interrupted()) {
++ throw new InterruptedException();
++ }
++ LockSupport.park(this);
++ }
++
++ return true;
++ }
++
++ @Override
++ public boolean isReleasable() {
++ return this.thread == null;
++ }
+ }
+}
diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/Cancellable.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/Cancellable.java
@@ -1938,374 +2311,85 @@ index 0000000000000000000000000000000000000000..11449056361bb6c5a055f543cdd135c4
+ */
+ public boolean cancel();
+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/DelayedPrioritisedTask.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/DelayedPrioritisedTask.java
+diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java
new file mode 100644
-index 0000000000000000000000000000000000000000..3ce10053d4ec51855ad7012abb5d97df1c0e557a
+index 0000000000000000000000000000000000000000..17cbaee1e89bd3f6d905e640d20d0119ab0570a0
--- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/DelayedPrioritisedTask.java
-@@ -0,0 +1,170 @@
-+package ca.spottedleaf.concurrentutil.executor.standard;
++++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java
+@@ -0,0 +1,271 @@
++package ca.spottedleaf.concurrentutil.executor;
+
-+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
-+import java.lang.invoke.VarHandle;
++import ca.spottedleaf.concurrentutil.util.Priority;
+
-+public class DelayedPrioritisedTask {
++public interface PrioritisedExecutor {
+
-+ protected volatile int priority;
-+ protected static final VarHandle PRIORITY_HANDLE = ConcurrentUtil.getVarHandle(DelayedPrioritisedTask.class, "priority", int.class);
++ /**
++ * Returns the number of tasks that have been scheduled are pending to be scheduled.
++ */
++ public long getTotalTasksScheduled();
+
-+ protected static final int PRIORITY_SET = Integer.MIN_VALUE >>> 0;
++ /**
++ * Returns the number of tasks that have been executed.
++ */
++ public long getTotalTasksExecuted();
+
-+ protected final int getPriorityVolatile() {
-+ return (int)PRIORITY_HANDLE.getVolatile((DelayedPrioritisedTask)this);
-+ }
-+
-+ protected final int compareAndExchangePriorityVolatile(final int expect, final int update) {
-+ return (int)PRIORITY_HANDLE.compareAndExchange((DelayedPrioritisedTask)this, (int)expect, (int)update);
-+ }
-+
-+ protected final int getAndOrPriorityVolatile(final int val) {
-+ return (int)PRIORITY_HANDLE.getAndBitwiseOr((DelayedPrioritisedTask)this, (int)val);
-+ }
-+
-+ protected final void setPriorityPlain(final int val) {
-+ PRIORITY_HANDLE.set((DelayedPrioritisedTask)this, (int)val);
-+ }
-+
-+ protected volatile PrioritisedExecutor.PrioritisedTask task;
-+ protected static final VarHandle TASK_HANDLE = ConcurrentUtil.getVarHandle(DelayedPrioritisedTask.class, "task", PrioritisedExecutor.PrioritisedTask.class);
-+
-+ protected PrioritisedExecutor.PrioritisedTask getTaskPlain() {
-+ return (PrioritisedExecutor.PrioritisedTask)TASK_HANDLE.get((DelayedPrioritisedTask)this);
-+ }
-+
-+ protected PrioritisedExecutor.PrioritisedTask getTaskVolatile() {
-+ return (PrioritisedExecutor.PrioritisedTask)TASK_HANDLE.getVolatile((DelayedPrioritisedTask)this);
-+ }
-+
-+ protected final PrioritisedExecutor.PrioritisedTask compareAndExchangeTaskVolatile(final PrioritisedExecutor.PrioritisedTask expect, final PrioritisedExecutor.PrioritisedTask update) {
-+ return (PrioritisedExecutor.PrioritisedTask)TASK_HANDLE.compareAndExchange((DelayedPrioritisedTask)this, (PrioritisedExecutor.PrioritisedTask)expect, (PrioritisedExecutor.PrioritisedTask)update);
-+ }
-+
-+ public DelayedPrioritisedTask(final PrioritisedExecutor.Priority priority) {
-+ this.setPriorityPlain(priority.priority);
-+ }
-+
-+ // only public for debugging
-+ public int getPriorityInternal() {
-+ return this.getPriorityVolatile();
-+ }
-+
-+ public PrioritisedExecutor.PrioritisedTask getTask() {
-+ return this.getTaskVolatile();
-+ }
-+
-+ public void setTask(final PrioritisedExecutor.PrioritisedTask task) {
-+ int priority = this.getPriorityVolatile();
-+
-+ if (this.compareAndExchangeTaskVolatile(null, task) != null) {
-+ throw new IllegalStateException("setTask() called twice");
-+ }
-+
-+ int failures = 0;
-+ for (;;) {
-+ task.setPriority(PrioritisedExecutor.Priority.getPriority(priority));
-+
-+ if (priority == (priority = this.compareAndExchangePriorityVolatile(priority, priority | PRIORITY_SET))) {
-+ return;
-+ }
-+
-+ ++failures;
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+ }
-+ }
-+
-+ public PrioritisedExecutor.Priority getPriority() {
-+ final int priority = this.getPriorityVolatile();
-+ if ((priority & PRIORITY_SET) != 0) {
-+ return this.task.getPriority();
-+ }
-+
-+ return PrioritisedExecutor.Priority.getPriority(priority);
-+ }
-+
-+ public void raisePriority(final PrioritisedExecutor.Priority priority) {
-+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
-+ throw new IllegalArgumentException("Invalid priority " + priority);
-+ }
-+
-+ int failures = 0;
-+ for (int curr = this.getPriorityVolatile();;) {
-+ if ((curr & PRIORITY_SET) != 0) {
-+ this.getTaskPlain().raisePriority(priority);
-+ return;
-+ }
-+
-+ if (!priority.isLowerPriority(curr)) {
-+ return;
-+ }
-+
-+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) {
-+ return;
-+ }
-+
-+ // failed, retry
-+
-+ ++failures;
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+ }
-+ }
-+
-+ public void setPriority(final PrioritisedExecutor.Priority priority) {
-+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
-+ throw new IllegalArgumentException("Invalid priority " + priority);
-+ }
-+
-+ int failures = 0;
-+ for (int curr = this.getPriorityVolatile();;) {
-+ if ((curr & PRIORITY_SET) != 0) {
-+ this.getTaskPlain().setPriority(priority);
-+ return;
-+ }
-+
-+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) {
-+ return;
-+ }
-+
-+ // failed, retry
-+
-+ ++failures;
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+ }
-+ }
-+
-+ public void lowerPriority(final PrioritisedExecutor.Priority priority) {
-+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
-+ throw new IllegalArgumentException("Invalid priority " + priority);
-+ }
-+
-+ int failures = 0;
-+ for (int curr = this.getPriorityVolatile();;) {
-+ if ((curr & PRIORITY_SET) != 0) {
-+ this.getTaskPlain().lowerPriority(priority);
-+ return;
-+ }
-+
-+ if (!priority.isHigherPriority(curr)) {
-+ return;
-+ }
-+
-+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority.priority))) {
-+ return;
-+ }
-+
-+ // failed, retry
-+
-+ ++failures;
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+ }
-+ }
-+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedExecutor.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedExecutor.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..91beb6f23f257cf265fe3150f760892e605f217a
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedExecutor.java
-@@ -0,0 +1,276 @@
-+package ca.spottedleaf.concurrentutil.executor.standard;
-+
-+import ca.spottedleaf.concurrentutil.executor.BaseExecutor;
-+
-+/**
-+ * Implementation of {@link BaseExecutor} which schedules tasks to be executed by a given priority.
-+ * @see BaseExecutor
-+ */
-+public interface PrioritisedExecutor extends BaseExecutor {
-+
-+ public static enum Priority {
-+
-+ /**
-+ * Priority value indicating the task has completed or is being completed.
-+ * This priority cannot be used to schedule tasks.
-+ */
-+ COMPLETING(-1),
-+
-+ /**
-+ * Absolute highest priority, should only be used for when a task is blocking a time-critical thread.
-+ */
-+ BLOCKING(),
-+
-+ /**
-+ * Should only be used for urgent but not time-critical tasks.
-+ */
-+ HIGHEST(),
-+
-+ /**
-+ * Two priorities above normal.
-+ */
-+ HIGHER(),
-+
-+ /**
-+ * One priority above normal.
-+ */
-+ HIGH(),
-+
-+ /**
-+ * Default priority.
-+ */
-+ NORMAL(),
-+
-+ /**
-+ * One priority below normal.
-+ */
-+ LOW(),
-+
-+ /**
-+ * Two priorities below normal.
-+ */
-+ LOWER(),
-+
-+ /**
-+ * Use for tasks that should eventually execute, but are not needed to.
-+ */
-+ LOWEST(),
-+
-+ /**
-+ * Use for tasks that can be delayed indefinitely.
-+ */
-+ IDLE();
-+
-+ // returns whether the priority can be scheduled
-+ public static boolean isValidPriority(final Priority priority) {
-+ return priority != null && priority != Priority.COMPLETING;
-+ }
-+
-+ // returns the higher priority of the two
-+ public static Priority max(final Priority p1, final Priority p2) {
-+ return p1.isHigherOrEqualPriority(p2) ? p1 : p2;
-+ }
-+
-+ // returns the lower priroity of the two
-+ public static Priority min(final Priority p1, final Priority p2) {
-+ return p1.isLowerOrEqualPriority(p2) ? p1 : p2;
-+ }
-+
-+ public boolean isHigherOrEqualPriority(final Priority than) {
-+ return this.priority <= than.priority;
-+ }
-+
-+ public boolean isHigherPriority(final Priority than) {
-+ return this.priority < than.priority;
-+ }
-+
-+ public boolean isLowerOrEqualPriority(final Priority than) {
-+ return this.priority >= than.priority;
-+ }
-+
-+ public boolean isLowerPriority(final Priority than) {
-+ return this.priority > than.priority;
-+ }
-+
-+ public boolean isHigherOrEqualPriority(final int than) {
-+ return this.priority <= than;
-+ }
-+
-+ public boolean isHigherPriority(final int than) {
-+ return this.priority < than;
-+ }
-+
-+ public boolean isLowerOrEqualPriority(final int than) {
-+ return this.priority >= than;
-+ }
-+
-+ public boolean isLowerPriority(final int than) {
-+ return this.priority > than;
-+ }
-+
-+ public static boolean isHigherOrEqualPriority(final int priority, final int than) {
-+ return priority <= than;
-+ }
-+
-+ public static boolean isHigherPriority(final int priority, final int than) {
-+ return priority < than;
-+ }
-+
-+ public static boolean isLowerOrEqualPriority(final int priority, final int than) {
-+ return priority >= than;
-+ }
-+
-+ public static boolean isLowerPriority(final int priority, final int than) {
-+ return priority > than;
-+ }
-+
-+ static final Priority[] PRIORITIES = Priority.values();
-+
-+ /** includes special priorities */
-+ public static final int TOTAL_PRIORITIES = PRIORITIES.length;
-+
-+ public static final int TOTAL_SCHEDULABLE_PRIORITIES = TOTAL_PRIORITIES - 1;
-+
-+ public static Priority getPriority(final int priority) {
-+ return PRIORITIES[priority + 1];
-+ }
-+
-+ private static int priorityCounter;
-+
-+ private static int nextCounter() {
-+ return priorityCounter++;
-+ }
-+
-+ public final int priority;
-+
-+ Priority() {
-+ this(nextCounter());
-+ }
-+
-+ Priority(final int priority) {
-+ this.priority = priority;
-+ }
-+ }
++ /**
++ * Generates the next suborder id.
++ * @return The next suborder id.
++ */
++ public long generateNextSubOrder();
+
+ /**
+ * Executes the next available task.
+ *
-+ * If there is a task with priority {@link PrioritisedExecutor.Priority#BLOCKING} available, then that such task is executed.
++ * If there is a task with priority {@link Priority#BLOCKING} available, then that such task is executed.
+ *
+ *
-+ * If there is a task with priority {@link PrioritisedExecutor.Priority#IDLE} available then that task is only executed
++ * If there is a task with priority {@link Priority#IDLE} available then that task is only executed
+ * when there are no other tasks available with a higher priority.
+ *
+ *
-+ * If there are no tasks that have priority {@link PrioritisedExecutor.Priority#BLOCKING} or {@link PrioritisedExecutor.Priority#IDLE}, then
++ * If there are no tasks that have priority {@link Priority#BLOCKING} or {@link Priority#IDLE}, then
+ * this function will be biased to execute tasks that have higher priorities.
+ *
+ *
+ * @return {@code true} if a task was executed, {@code false} otherwise
+ * @throws IllegalStateException If the current thread is not allowed to execute a task
+ */
-+ @Override
+ public boolean executeTask() throws IllegalStateException;
+
+ /**
++ * Prevent further additions to this executor. Attempts to add after this call has completed (potentially during) will
++ * result in {@link IllegalStateException} being thrown.
++ *
++ * This operation is atomic with respect to other shutdown calls
++ *
++ *
++ * After this call has completed, regardless of return value, this executor will be shutdown.
++ *
++ *
++ * @return {@code true} if the executor was shutdown, {@code false} if it has shut down already
++ * @see #isShutdown()
++ */
++ public boolean shutdown();
++
++ /**
++ * Returns whether this executor has shut down. Effectively, returns whether new tasks will be rejected.
++ * This method does not indicate whether all the tasks scheduled have been executed.
++ * @return Returns whether this executor has shut down.
++ */
++ public boolean isShutdown();
++
++ /**
+ * Queues or executes a task at {@link Priority#NORMAL} priority.
+ * @param task The task to run.
+ *
-+ * @throws IllegalStateException If this queue has shutdown.
++ * @throws IllegalStateException If this executor has shutdown.
+ * @throws NullPointerException If the task is null
+ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task
-+ * associated with the parameter
++ * associated with the parameter
+ */
-+ public default PrioritisedTask queueRunnable(final Runnable task) {
-+ return this.queueRunnable(task, Priority.NORMAL);
-+ }
++ public PrioritisedTask queueTask(final Runnable task);
+
+ /**
+ * Queues or executes a task.
@@ -2313,50 +2397,107 @@ index 0000000000000000000000000000000000000000..91beb6f23f257cf265fe3150f760892e
+ * @param task The task to run.
+ * @param priority The priority for the task.
+ *
-+ * @throws IllegalStateException If this queue has shutdown.
++ * @throws IllegalStateException If this executor has shutdown.
+ * @throws NullPointerException If the task is null
+ * @throws IllegalArgumentException If the priority is invalid.
+ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task
-+ * associated with the parameter
++ * associated with the parameter
+ */
-+ public PrioritisedTask queueRunnable(final Runnable task, final Priority priority);
++ public PrioritisedTask queueTask(final Runnable task, final Priority priority);
+
+ /**
-+ * Creates, but does not execute or queue the task. The task must later be queued via {@link BaseTask#queue()}.
++ * Queues or executes a task.
+ *
+ * @param task The task to run.
++ * @param priority The priority for the task.
++ * @param subOrder The task's suborder.
+ *
-+ * @throws IllegalStateException If this queue has shutdown.
++ * @throws IllegalStateException If this executor has shutdown.
+ * @throws NullPointerException If the task is null
+ * @throws IllegalArgumentException If the priority is invalid.
-+ * @throws UnsupportedOperationException If this executor does not support lazily queueing tasks
-+ * @return The prioritised task associated with the parameters
++ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task
++ * associated with the parameter
+ */
-+ public default PrioritisedTask createTask(final Runnable task) {
-+ return this.createTask(task, Priority.NORMAL);
-+ }
++ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder);
+
+ /**
-+ * Creates, but does not execute or queue the task. The task must later be queued via {@link BaseTask#queue()}.
++ * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority.
++ * @param task The task to run.
++ *
++ * @throws NullPointerException If the task is null
++ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task
++ * associated with the parameter
++ */
++ public PrioritisedTask createTask(final Runnable task);
++
++ /**
++ * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority.
+ *
+ * @param task The task to run.
+ * @param priority The priority for the task.
+ *
-+ * @throws IllegalStateException If this queue has shutdown.
+ * @throws NullPointerException If the task is null
+ * @throws IllegalArgumentException If the priority is invalid.
-+ * @throws UnsupportedOperationException If this executor does not support lazily queueing tasks
-+ * @return The prioritised task associated with the parameters
++ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task
++ * associated with the parameter
+ */
+ public PrioritisedTask createTask(final Runnable task, final Priority priority);
+
+ /**
-+ * Extension of {@link ca.spottedleaf.concurrentutil.executor.BaseExecutor.BaseTask} which adds functions
-+ * to retrieve and modify the task's associated priority.
++ * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority.
+ *
-+ * @see ca.spottedleaf.concurrentutil.executor.BaseExecutor.BaseTask
++ * @param task The task to run.
++ * @param priority The priority for the task.
++ * @param subOrder The task's suborder.
++ *
++ * @throws NullPointerException If the task is null
++ * @throws IllegalArgumentException If the priority is invalid.
++ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task
++ * associated with the parameter
+ */
-+ public static interface PrioritisedTask extends BaseTask {
++ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder);
++
++ public static interface PrioritisedTask extends Cancellable {
++
++ /**
++ * Returns the executor associated with this task.
++ * @return The executor associated with this task.
++ */
++ public PrioritisedExecutor getExecutor();
++
++ /**
++ * Causes a lazily queued task to become queued or executed
++ *
++ * @throws IllegalStateException If the backing executor has shutdown
++ * @return {@code true} If the task was queued, {@code false} if the task was already queued/cancelled/executed
++ */
++ public boolean queue();
++
++ /**
++ * Returns whether this task has been queued and is not completing.
++ * @return {@code true} If the task has been queued, {@code false} if the task has not been queued or is marked
++ * as completing.
++ */
++ public boolean isQueued();
++
++ /**
++ * Forces this task to be marked as completed.
++ *
++ * @return {@code true} if the task was cancelled, {@code false} if the task has already completed
++ * or is being completed.
++ */
++ @Override
++ public boolean cancel();
++
++ /**
++ * Executes this task. This will also mark the task as completing.
++ *
++ * Exceptions thrown from the runnable will be rethrown.
++ *
++ *
++ * @return {@code true} if this task was executed, {@code false} if it was already marked as completed.
++ */
++ public boolean execute();
+
+ /**
+ * Returns the current priority. Note that {@link Priority#COMPLETING} will be returned
@@ -2371,7 +2512,7 @@ index 0000000000000000000000000000000000000000..91beb6f23f257cf265fe3150f760892e
+ *
+ * @throws IllegalArgumentException If the priority is invalid
+ * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue
-+ * this task was scheduled on was shutdown, or if the priority was already at the specified level.
++ * this task was scheduled on was shutdown, or if the priority was already at the specified level.
+ */
+ public boolean setPriority(final Priority priority);
+
@@ -2381,7 +2522,8 @@ index 0000000000000000000000000000000000000000..91beb6f23f257cf265fe3150f760892e
+ * @param priority Priority specified
+ *
+ * @throws IllegalArgumentException If the priority is invalid
-+ * @return {@code false} if the current task is completing, {@code true} if the priority was raised to the specified level or was already at the specified level or higher.
++ * @return {@code false} if the current task is completing, {@code true} if the priority was raised to the
++ * specified level or was already at the specified level or higher.
+ */
+ public boolean raisePriority(final Priority priority);
+
@@ -2391,20 +2533,532 @@ index 0000000000000000000000000000000000000000..91beb6f23f257cf265fe3150f760892e
+ * @param priority Priority specified
+ *
+ * @throws IllegalArgumentException If the priority is invalid
-+ * @return {@code false} if the current task is completing, {@code true} if the priority was lowered to the specified level or was already at the specified level or lower.
++ * @return {@code false} if the current task is completing, {@code true} if the priority was lowered to the
++ * specified level or was already at the specified level or lower.
+ */
+ public boolean lowerPriority(final Priority priority);
++
++ /**
++ * Returns the suborder id associated with this task.
++ * @return The suborder id associated with this task.
++ */
++ public long getSubOrder();
++
++ /**
++ * Sets the suborder id associated with this task. Ths function has no effect when this task
++ * is completing or is completed.
++ *
++ * @param subOrder Specified new sub order.
++ *
++ * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue
++ * this task was scheduled on was shutdown, or if the current suborder is the same as the new sub order.
++ */
++ public boolean setSubOrder(final long subOrder);
++
++ /**
++ * Attempts to raise the suborder to the suborder specified.
++ *
++ * @param subOrder Specified new sub order.
++ *
++ * @return {@code false} if the current task is completing, {@code true} if the suborder was raised to the
++ * specified suborder or was already at the specified suborder or higher.
++ */
++ public boolean raiseSubOrder(final long subOrder);
++
++ /**
++ * Attempts to lower the suborder to the suborder specified.
++ *
++ * @param subOrder Specified new sub order.
++ *
++ * @return {@code false} if the current task is completing, {@code true} if the suborder was lowered to the
++ * specified suborder or was already at the specified suborder or lower.
++ */
++ public boolean lowerSubOrder(final long subOrder);
++
++ /**
++ * Sets the priority and suborder id associated with this task. Ths function has no effect when this task
++ * is completing or is completed.
++ *
++ * @param priority Priority specified
++ * @param subOrder Specified new sub order.
++ * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue
++ * this task was scheduled on was shutdown, or if the current priority and suborder are the same as
++ * the parameters.
++ */
++ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder);
+ }
+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java
+diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java
new file mode 100644
-index 0000000000000000000000000000000000000000..d1683ba6350e530373944f98192c0f2baf241e70
+index 0000000000000000000000000000000000000000..edb8c6611bdc9aced2714b963e00bbb7829603d2
--- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java
-@@ -0,0 +1,301 @@
-+package ca.spottedleaf.concurrentutil.executor.standard;
++++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java
+@@ -0,0 +1,454 @@
++package ca.spottedleaf.concurrentutil.executor.queue;
+
++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
++import ca.spottedleaf.concurrentutil.util.Priority;
++import java.lang.invoke.VarHandle;
++import java.util.Comparator;
++import java.util.Map;
++import java.util.concurrent.ConcurrentSkipListMap;
++import java.util.concurrent.atomic.AtomicBoolean;
++import java.util.concurrent.atomic.AtomicLong;
++
++public final class PrioritisedTaskQueue implements PrioritisedExecutor {
++
++ /**
++ * Required for tie-breaking in the queue
++ */
++ private final AtomicLong taskIdGenerator = new AtomicLong();
++ private final AtomicLong scheduledTasks = new AtomicLong();
++ private final AtomicLong executedTasks = new AtomicLong();
++ private final AtomicLong subOrderGenerator = new AtomicLong();
++ private final AtomicBoolean shutdown = new AtomicBoolean();
++ private final ConcurrentSkipListMap tasks = new ConcurrentSkipListMap<>(PrioritisedQueuedTask.COMPARATOR);
++
++ @Override
++ public long getTotalTasksScheduled() {
++ return this.scheduledTasks.get();
++ }
++
++ @Override
++ public long getTotalTasksExecuted() {
++ return this.executedTasks.get();
++ }
++
++ @Override
++ public long generateNextSubOrder() {
++ return this.subOrderGenerator.getAndIncrement();
++ }
++
++ @Override
++ public boolean shutdown() {
++ return !this.shutdown.getAndSet(true);
++ }
++
++ @Override
++ public boolean isShutdown() {
++ return this.shutdown.get();
++ }
++
++ public PrioritisedTask peekFirst() {
++ final Map.Entry firstEntry = this.tasks.firstEntry();
++ return firstEntry == null ? null : firstEntry.getKey().task;
++ }
++
++ public Priority getHighestPriority() {
++ final Map.Entry firstEntry = this.tasks.firstEntry();
++ return firstEntry == null ? null : Priority.getPriority(firstEntry.getKey().priority);
++ }
++
++ public boolean hasNoScheduledTasks() {
++ final long executedTasks = this.executedTasks.get();
++ final long scheduledTasks = this.scheduledTasks.get();
++
++ return executedTasks == scheduledTasks;
++ }
++
++ public PrioritySubOrderPair getHighestPrioritySubOrder() {
++ final Map.Entry firstEntry = this.tasks.firstEntry();
++ if (firstEntry == null) {
++ return null;
++ }
++
++ final PrioritisedQueuedTask.Holder holder = firstEntry.getKey();
++
++ return new PrioritySubOrderPair(Priority.getPriority(holder.priority), holder.subOrder);
++ }
++
++ public Runnable pollTask() {
++ for (;;) {
++ final Map.Entry firstEntry = this.tasks.pollFirstEntry();
++ if (firstEntry != null) {
++ final PrioritisedQueuedTask.Holder task = firstEntry.getKey();
++ task.markRemoved();
++ if (!task.task.cancel()) {
++ continue;
++ }
++ return task.task.execute;
++ }
++
++ return null;
++ }
++ }
++
++ @Override
++ public boolean executeTask() {
++ for (;;) {
++ final Map.Entry firstEntry = this.tasks.pollFirstEntry();
++ if (firstEntry != null) {
++ final PrioritisedQueuedTask.Holder task = firstEntry.getKey();
++ task.markRemoved();
++ if (!task.task.execute()) {
++ continue;
++ }
++ return true;
++ }
++
++ return false;
++ }
++ }
++
++ @Override
++ public PrioritisedTask createTask(final Runnable task) {
++ return this.createTask(task, Priority.NORMAL, this.generateNextSubOrder());
++ }
++
++ @Override
++ public PrioritisedTask createTask(final Runnable task, final Priority priority) {
++ return this.createTask(task, priority, this.generateNextSubOrder());
++ }
++
++ @Override
++ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) {
++ return new PrioritisedQueuedTask(task, priority, subOrder);
++ }
++
++ @Override
++ public PrioritisedTask queueTask(final Runnable task) {
++ return this.queueTask(task, Priority.NORMAL, this.generateNextSubOrder());
++ }
++
++ @Override
++ public PrioritisedTask queueTask(final Runnable task, final Priority priority) {
++ return this.queueTask(task, priority, this.generateNextSubOrder());
++ }
++
++ @Override
++ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) {
++ final PrioritisedQueuedTask ret = new PrioritisedQueuedTask(task, priority, subOrder);
++
++ ret.queue();
++
++ return ret;
++ }
++
++ private final class PrioritisedQueuedTask implements PrioritisedExecutor.PrioritisedTask {
++ public static final Comparator COMPARATOR = (final PrioritisedQueuedTask.Holder t1, final PrioritisedQueuedTask.Holder t2) -> {
++ final int priorityCompare = t1.priority - t2.priority;
++ if (priorityCompare != 0) {
++ return priorityCompare;
++ }
++
++ final int subOrderCompare = Long.compare(t1.subOrder, t2.subOrder);
++ if (subOrderCompare != 0) {
++ return subOrderCompare;
++ }
++
++ return Long.compare(t1.id, t2.id);
++ };
++
++ private static final class Holder {
++ private final PrioritisedQueuedTask task;
++ private final int priority;
++ private final long subOrder;
++ private final long id;
++
++ private volatile boolean removed;
++ private static final VarHandle REMOVED_HANDLE = ConcurrentUtil.getVarHandle(Holder.class, "removed", boolean.class);
++
++ private Holder(final PrioritisedQueuedTask task, final int priority, final long subOrder,
++ final long id) {
++ this.task = task;
++ this.priority = priority;
++ this.subOrder = subOrder;
++ this.id = id;
++ }
++
++ /**
++ * Returns true if marked as removed
++ */
++ public boolean markRemoved() {
++ return !(boolean)REMOVED_HANDLE.getAndSet((Holder)this, (boolean)true);
++ }
++ }
++
++ private final long id;
++ private final Runnable execute;
++
++ private Priority priority;
++ private long subOrder;
++ private Holder holder;
++
++ public PrioritisedQueuedTask(final Runnable execute, final Priority priority, final long subOrder) {
++ if (!Priority.isValidPriority(priority)) {
++ throw new IllegalArgumentException("Invalid priority " + priority);
++ }
++
++ this.execute = execute;
++ this.priority = priority;
++ this.subOrder = subOrder;
++ this.id = PrioritisedTaskQueue.this.taskIdGenerator.getAndIncrement();
++ }
++
++ @Override
++ public PrioritisedExecutor getExecutor() {
++ return PrioritisedTaskQueue.this;
++ }
++
++ @Override
++ public boolean queue() {
++ synchronized (this) {
++ if (this.holder != null || this.priority == Priority.COMPLETING) {
++ return false;
++ }
++
++ if (PrioritisedTaskQueue.this.isShutdown()) {
++ throw new IllegalStateException("Queue is shutdown");
++ }
++
++ final Holder holder = new Holder(this, this.priority.priority, this.subOrder, this.id);
++ this.holder = holder;
++
++ PrioritisedTaskQueue.this.scheduledTasks.getAndIncrement();
++ PrioritisedTaskQueue.this.tasks.put(holder, Boolean.TRUE);
++ }
++
++ if (PrioritisedTaskQueue.this.isShutdown()) {
++ this.cancel();
++ throw new IllegalStateException("Queue is shutdown");
++ }
++
++
++ return true;
++ }
++
++ @Override
++ public boolean isQueued() {
++ synchronized (this) {
++ return this.holder != null && this.priority != Priority.COMPLETING;
++ }
++ }
++
++ @Override
++ public boolean cancel() {
++ synchronized (this) {
++ if (this.priority == Priority.COMPLETING) {
++ return false;
++ }
++
++ this.priority = Priority.COMPLETING;
++
++ if (this.holder != null) {
++ if (this.holder.markRemoved()) {
++ PrioritisedTaskQueue.this.tasks.remove(this.holder);
++ }
++ PrioritisedTaskQueue.this.executedTasks.getAndIncrement();
++ }
++
++ return true;
++ }
++ }
++
++ @Override
++ public boolean execute() {
++ final boolean increaseExecuted;
++
++ synchronized (this) {
++ if (this.priority == Priority.COMPLETING) {
++ return false;
++ }
++
++ this.priority = Priority.COMPLETING;
++
++ if (increaseExecuted = (this.holder != null)) {
++ if (this.holder.markRemoved()) {
++ PrioritisedTaskQueue.this.tasks.remove(this.holder);
++ }
++ }
++ }
++
++ try {
++ this.execute.run();
++ return true;
++ } finally {
++ if (increaseExecuted) {
++ PrioritisedTaskQueue.this.executedTasks.getAndIncrement();
++ }
++ }
++ }
++
++ @Override
++ public Priority getPriority() {
++ synchronized (this) {
++ return this.priority;
++ }
++ }
++
++ @Override
++ public boolean setPriority(final Priority priority) {
++ synchronized (this) {
++ if (this.priority == Priority.COMPLETING || this.priority == priority) {
++ return false;
++ }
++
++ this.priority = priority;
++
++ if (this.holder != null) {
++ if (this.holder.markRemoved()) {
++ PrioritisedTaskQueue.this.tasks.remove(this.holder);
++ }
++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id);
++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
++ }
++
++ return true;
++ }
++ }
++
++ @Override
++ public boolean raisePriority(final Priority priority) {
++ synchronized (this) {
++ if (this.priority == Priority.COMPLETING || this.priority.isHigherOrEqualPriority(priority)) {
++ return false;
++ }
++
++ this.priority = priority;
++
++ if (this.holder != null) {
++ if (this.holder.markRemoved()) {
++ PrioritisedTaskQueue.this.tasks.remove(this.holder);
++ }
++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id);
++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
++ }
++
++ return true;
++ }
++ }
++
++ @Override
++ public boolean lowerPriority(Priority priority) {
++ synchronized (this) {
++ if (this.priority == Priority.COMPLETING || this.priority.isLowerOrEqualPriority(priority)) {
++ return false;
++ }
++
++ this.priority = priority;
++
++ if (this.holder != null) {
++ if (this.holder.markRemoved()) {
++ PrioritisedTaskQueue.this.tasks.remove(this.holder);
++ }
++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id);
++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
++ }
++
++ return true;
++ }
++ }
++
++ @Override
++ public long getSubOrder() {
++ synchronized (this) {
++ return this.subOrder;
++ }
++ }
++
++ @Override
++ public boolean setSubOrder(final long subOrder) {
++ synchronized (this) {
++ if (this.priority == Priority.COMPLETING || this.subOrder == subOrder) {
++ return false;
++ }
++
++ this.subOrder = subOrder;
++
++ if (this.holder != null) {
++ if (this.holder.markRemoved()) {
++ PrioritisedTaskQueue.this.tasks.remove(this.holder);
++ }
++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id);
++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
++ }
++
++ return true;
++ }
++ }
++
++ @Override
++ public boolean raiseSubOrder(long subOrder) {
++ synchronized (this) {
++ if (this.priority == Priority.COMPLETING || this.subOrder >= subOrder) {
++ return false;
++ }
++
++ this.subOrder = subOrder;
++
++ if (this.holder != null) {
++ if (this.holder.markRemoved()) {
++ PrioritisedTaskQueue.this.tasks.remove(this.holder);
++ }
++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id);
++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
++ }
++
++ return true;
++ }
++ }
++
++ @Override
++ public boolean lowerSubOrder(final long subOrder) {
++ synchronized (this) {
++ if (this.priority == Priority.COMPLETING || this.subOrder <= subOrder) {
++ return false;
++ }
++
++ this.subOrder = subOrder;
++
++ if (this.holder != null) {
++ if (this.holder.markRemoved()) {
++ PrioritisedTaskQueue.this.tasks.remove(this.holder);
++ }
++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id);
++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
++ }
++
++ return true;
++ }
++ }
++
++ @Override
++ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) {
++ synchronized (this) {
++ if (this.priority == Priority.COMPLETING || (this.priority == priority && this.subOrder == subOrder)) {
++ return false;
++ }
++
++ this.priority = priority;
++ this.subOrder = subOrder;
++
++ if (this.holder != null) {
++ if (this.holder.markRemoved()) {
++ PrioritisedTaskQueue.this.tasks.remove(this.holder);
++ }
++ this.holder = new Holder(this, priority.priority, this.subOrder, this.id);
++ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
++ }
++
++ return true;
++ }
++ }
++ }
++
++ public static record PrioritySubOrderPair(Priority priority, long subOrder) {}
++}
+diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..f5367a13aaa02f0f929813c00a67e6ac7c8652cb
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java
+@@ -0,0 +1,402 @@
++package ca.spottedleaf.concurrentutil.executor.thread;
++
++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
++import ca.spottedleaf.concurrentutil.util.Priority;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.lang.invoke.VarHandle;
@@ -2415,8 +3069,7 @@ index 0000000000000000000000000000000000000000..d1683ba6350e530373944f98192c0f2b
+ *
+ * Note: When using this thread, queue additions to the underlying {@link #queue} are not sufficient to get this thread
+ * to execute the task. The function {@link #notifyTasks()} must be used after scheduling a task. For expected behaviour
-+ * of task scheduling (thread wakes up after tasks are scheduled), use the methods provided on {@link PrioritisedExecutor}
-+ * methods.
++ * of task scheduling, use the methods provided on this class to schedule tasks.
+ *
+ */
+public class PrioritisedQueueExecutorThread extends Thread implements PrioritisedExecutor {
@@ -2434,7 +3087,7 @@ index 0000000000000000000000000000000000000000..d1683ba6350e530373944f98192c0f2b
+
+ protected final long spinWaitTime;
+
-+ static final long DEFAULT_SPINWAIT_TIME = (long)(0.1e6);// 0.1ms
++ protected static final long DEFAULT_SPINWAIT_TIME = (long)(0.1e6);// 0.1ms
+
+ public PrioritisedQueueExecutorThread(final PrioritisedExecutor queue) {
+ this(queue, DEFAULT_SPINWAIT_TIME); // 0.1ms
@@ -2446,7 +3099,16 @@ index 0000000000000000000000000000000000000000..d1683ba6350e530373944f98192c0f2b
+ }
+
+ @Override
-+ public void run() {
++ public final void run() {
++ try {
++ this.begin();
++ this.doRun();
++ } finally {
++ this.die();
++ }
++ }
++
++ public final void doRun() {
+ final long spinWaitTime = this.spinWaitTime;
+
+ main_loop:
@@ -2484,7 +3146,7 @@ index 0000000000000000000000000000000000000000..d1683ba6350e530373944f98192c0f2b
+ this.setThreadParkedVolatile(true);
+
+ // We need to parse here to avoid a race condition where a thread queues a task before we set parked to true
-+ // (i.e it will not notify us)
++ // (i.e. it will not notify us)
+ if (this.pollTasks()) {
+ this.setThreadParkedVolatile(false);
+ continue;
@@ -2503,6 +3165,10 @@ index 0000000000000000000000000000000000000000..d1683ba6350e530373944f98192c0f2b
+ }
+ }
+
++ protected void begin() {}
++
++ protected void die() {}
++
+ /**
+ * Attempts to poll as many tasks as possible, returning when finished.
+ * @return Whether any tasks were executed.
@@ -2519,8 +3185,6 @@ index 0000000000000000000000000000000000000000..d1683ba6350e530373944f98192c0f2b
+ break;
+ }
+ ret = true;
-+ } catch (final ThreadDeath death) {
-+ throw death; // goodbye world...
+ } catch (final Throwable throwable) {
+ LOGGER.error("Exception thrown from prioritized runnable task in thread '" + this.getName() + "'", throwable);
+ }
@@ -2550,67 +3214,6 @@ index 0000000000000000000000000000000000000000..d1683ba6350e530373944f98192c0f2b
+ }
+
+ @Override
-+ public PrioritisedTask createTask(final Runnable task, final Priority priority) {
-+ final PrioritisedTask queueTask = this.queue.createTask(task, priority);
-+
-+ // need to override queue() to notify us of tasks
-+ return new PrioritisedTask() {
-+ @Override
-+ public Priority getPriority() {
-+ return queueTask.getPriority();
-+ }
-+
-+ @Override
-+ public boolean setPriority(final Priority priority) {
-+ return queueTask.setPriority(priority);
-+ }
-+
-+ @Override
-+ public boolean raisePriority(final Priority priority) {
-+ return queueTask.raisePriority(priority);
-+ }
-+
-+ @Override
-+ public boolean lowerPriority(final Priority priority) {
-+ return queueTask.lowerPriority(priority);
-+ }
-+
-+ @Override
-+ public boolean queue() {
-+ final boolean ret = queueTask.queue();
-+ if (ret) {
-+ PrioritisedQueueExecutorThread.this.notifyTasks();
-+ }
-+ return ret;
-+ }
-+
-+ @Override
-+ public boolean cancel() {
-+ return queueTask.cancel();
-+ }
-+
-+ @Override
-+ public boolean execute() {
-+ return queueTask.execute();
-+ }
-+ };
-+ }
-+
-+ @Override
-+ public PrioritisedTask queueRunnable(final Runnable task, final Priority priority) {
-+ final PrioritisedTask ret = this.queue.queueRunnable(task, priority);
-+
-+ this.notifyTasks();
-+
-+ return ret;
-+ }
-+
-+ @Override
-+ public boolean haveAllTasksExecuted() {
-+ return this.queue.haveAllTasksExecuted();
-+ }
-+
-+ @Override
+ public long getTotalTasksExecuted() {
+ return this.queue.getTotalTasksExecuted();
+ }
@@ -2620,16 +3223,19 @@ index 0000000000000000000000000000000000000000..d1683ba6350e530373944f98192c0f2b
+ return this.queue.getTotalTasksScheduled();
+ }
+
-+ /**
-+ * {@inheritDoc}
-+ * @throws IllegalStateException If the current thread is {@code this} thread, or the underlying queue throws this exception.
-+ */
+ @Override
-+ public void waitUntilAllExecuted() throws IllegalStateException {
-+ if (Thread.currentThread() == this) {
-+ throw new IllegalStateException("Cannot block on our own queue");
-+ }
-+ this.queue.waitUntilAllExecuted();
++ public long generateNextSubOrder() {
++ return this.queue.generateNextSubOrder();
++ }
++
++ @Override
++ public boolean shutdown() {
++ throw new UnsupportedOperationException();
++ }
++
++ @Override
++ public boolean isShutdown() {
++ return false;
+ }
+
+ /**
@@ -2641,12 +3247,61 @@ index 0000000000000000000000000000000000000000..d1683ba6350e530373944f98192c0f2b
+ throw new IllegalStateException();
+ }
+
++ @Override
++ public PrioritisedTask queueTask(final Runnable task) {
++ final PrioritisedTask ret = this.createTask(task);
++
++ ret.queue();
++
++ return ret;
++ }
++
++ @Override
++ public PrioritisedTask queueTask(final Runnable task, final Priority priority) {
++ final PrioritisedTask ret = this.createTask(task, priority);
++
++ ret.queue();
++
++ return ret;
++ }
++
++ @Override
++ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) {
++ final PrioritisedTask ret = this.createTask(task, priority, subOrder);
++
++ ret.queue();
++
++ return ret;
++ }
++
++
++ @Override
++ public PrioritisedTask createTask(Runnable task) {
++ final PrioritisedTask queueTask = this.queue.createTask(task);
++
++ return new WrappedTask(queueTask);
++ }
++
++ @Override
++ public PrioritisedTask createTask(final Runnable task, final Priority priority) {
++ final PrioritisedTask queueTask = this.queue.createTask(task, priority);
++
++ return new WrappedTask(queueTask);
++ }
++
++ @Override
++ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) {
++ final PrioritisedTask queueTask = this.queue.createTask(task, priority, subOrder);
++
++ return new WrappedTask(queueTask);
++ }
++
+ /**
+ * Closes this queue executor's queue. Optionally waits for all tasks in queue to be executed if {@code wait} is true.
+ *
+ * This function is MT-Safe.
+ *
-+ * @param wait If this call is to wait until the queue is empty and there are no tasks executing in the queue.
++ * @param wait If this call is to wait until this thread shuts down.
+ * @param killQueue Whether to shutdown this thread's queue
+ * @return whether this thread shut down the queue
+ * @see #halt(boolean)
@@ -2660,7 +3315,20 @@ index 0000000000000000000000000000000000000000..d1683ba6350e530373944f98192c0f2b
+ LockSupport.unpark(this);
+
+ if (wait) {
-+ this.waitUntilAllExecuted();
++ boolean interrupted = false;
++ for (;;) {
++ if (this.isAlive()) {
++ if (interrupted) {
++ Thread.currentThread().interrupt();
++ }
++ break;
++ }
++ try {
++ this.join();
++ } catch (final InterruptedException ex) {
++ interrupted = true;
++ }
++ }
+ }
+
+ return ret;
@@ -2702,157 +3370,163 @@ index 0000000000000000000000000000000000000000..d1683ba6350e530373944f98192c0f2b
+ protected final void setThreadParkedVolatile(final boolean value) {
+ THREAD_PARKED_HANDLE.setVolatile(this, value);
+ }
-+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..2ba36e29d0d8693f2f5e6c6d195ca27f2a5099aa
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java
-@@ -0,0 +1,632 @@
-+package ca.spottedleaf.concurrentutil.executor.standard;
+
-+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
++ /**
++ * Required so that queue() can notify (unpark) this thread
++ */
++ private final class WrappedTask implements PrioritisedTask {
++ private final PrioritisedTask queueTask;
++
++ public WrappedTask(final PrioritisedTask queueTask) {
++ this.queueTask = queueTask;
++ }
++
++ @Override
++ public PrioritisedExecutor getExecutor() {
++ return PrioritisedQueueExecutorThread.this;
++ }
++
++ @Override
++ public boolean queue() {
++ final boolean ret = this.queueTask.queue();
++ if (ret) {
++ PrioritisedQueueExecutorThread.this.notifyTasks();
++ }
++ return ret;
++ }
++
++ @Override
++ public boolean isQueued() {
++ return this.queueTask.isQueued();
++ }
++
++ @Override
++ public boolean cancel() {
++ return this.queueTask.cancel();
++ }
++
++ @Override
++ public boolean execute() {
++ return this.queueTask.execute();
++ }
++
++ @Override
++ public Priority getPriority() {
++ return this.queueTask.getPriority();
++ }
++
++ @Override
++ public boolean setPriority(final Priority priority) {
++ return this.queueTask.setPriority(priority);
++ }
++
++ @Override
++ public boolean raisePriority(final Priority priority) {
++ return this.queueTask.raisePriority(priority);
++ }
++
++ @Override
++ public boolean lowerPriority(final Priority priority) {
++ return this.queueTask.lowerPriority(priority);
++ }
++
++ @Override
++ public long getSubOrder() {
++ return this.queueTask.getSubOrder();
++ }
++
++ @Override
++ public boolean setSubOrder(final long subOrder) {
++ return this.queueTask.setSubOrder(subOrder);
++ }
++
++ @Override
++ public boolean raiseSubOrder(final long subOrder) {
++ return this.queueTask.raiseSubOrder(subOrder);
++ }
++
++ @Override
++ public boolean lowerSubOrder(final long subOrder) {
++ return this.queueTask.lowerSubOrder(subOrder);
++ }
++
++ @Override
++ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) {
++ return this.queueTask.setPriorityAndSubOrder(priority, subOrder);
++ }
++ }
++}
+diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..cb9df914a9a6d0d3f58fa58d8c93f4f583416cd1
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java
+@@ -0,0 +1,741 @@
++package ca.spottedleaf.concurrentutil.executor.thread;
++
++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
++import ca.spottedleaf.concurrentutil.executor.queue.PrioritisedTaskQueue;
++import ca.spottedleaf.concurrentutil.util.Priority;
++import ca.spottedleaf.concurrentutil.util.TimeUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-+import java.util.ArrayList;
++import java.lang.reflect.Array;
+import java.util.Arrays;
-+import java.util.Comparator;
-+import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicBoolean;
-+import java.util.function.BiConsumer;
++import java.util.concurrent.atomic.AtomicLong;
++import java.util.function.Consumer;
+
+public final class PrioritisedThreadPool {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(PrioritisedThreadPool.class);
+
-+ private final PrioritisedThread[] threads;
-+ private final TreeSet queues = new TreeSet<>(PrioritisedPoolExecutorImpl.comparator());
-+ private final String name;
-+ private final long queueMaxHoldTime;
++ private final Consumer threadModifier;
++ private final COWArrayList executors = new COWArrayList<>(ExecutorGroup.class);
++ private final COWArrayList threads = new COWArrayList<>(PrioritisedThread.class);
++ private final COWArrayList aliveThreads = new COWArrayList<>(PrioritisedThread.class);
+
-+ private final ReferenceOpenHashSet nonShutdownQueues = new ReferenceOpenHashSet<>();
-+ private final ReferenceOpenHashSet activeQueues = new ReferenceOpenHashSet<>();
++ private static final Priority HIGH_PRIORITY_NOTIFY_THRESHOLD = Priority.HIGH;
++ private static final Priority QUEUE_SHUTDOWN_PRIORITY = Priority.HIGH;
+
+ private boolean shutdown;
+
-+ private long schedulingIdGenerator;
++ public PrioritisedThreadPool(final Consumer threadModifier) {
++ this.threadModifier = threadModifier;
+
-+ private static final long DEFAULT_QUEUE_HOLD_TIME = (long)(5.0e6);
-+
-+ /**
-+ * @param name Specified debug name of this thread pool
-+ * @param threads The number of threads to use
-+ */
-+ public PrioritisedThreadPool(final String name, final int threads) {
-+ this(name, threads, null);
-+ }
-+
-+ /**
-+ * @param name Specified debug name of this thread pool
-+ * @param threads The number of threads to use
-+ * @param threadModifier Invoked for each created thread with its incremental id before starting them
-+ */
-+ public PrioritisedThreadPool(final String name, final int threads, final BiConsumer threadModifier) {
-+ this(name, threads, threadModifier, DEFAULT_QUEUE_HOLD_TIME); // 5ms
-+ }
-+
-+ /**
-+ * @param name Specified debug name of this thread pool
-+ * @param threads The number of threads to use
-+ * @param threadModifier Invoked for each created thread with its incremental id before starting them
-+ * @param queueHoldTime The maximum amount of time to spend executing tasks in a specific queue before attempting
-+ * to switch to another queue, per thread
-+ */
-+ public PrioritisedThreadPool(final String name, final int threads, final BiConsumer threadModifier,
-+ final long queueHoldTime) { // in ns
-+ if (threads <= 0) {
-+ throw new IllegalArgumentException("Thread count must be > 0, not " + threads);
-+ }
-+ if (name == null) {
-+ throw new IllegalArgumentException("Name cannot be null");
-+ }
-+ this.name = name;
-+ this.queueMaxHoldTime = queueHoldTime;
-+
-+ this.threads = new PrioritisedThread[threads];
-+ for (int i = 0; i < threads; ++i) {
-+ this.threads[i] = new PrioritisedThread(this);
-+
-+ // set default attributes
-+ this.threads[i].setName("Prioritised thread for pool '" + name + "' #" + i);
-+ this.threads[i].setUncaughtExceptionHandler((final Thread thread, final Throwable throwable) -> {
-+ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable);
-+ });
-+
-+ // let thread modifier override defaults
-+ if (threadModifier != null) {
-+ threadModifier.accept(this.threads[i], Integer.valueOf(i));
-+ }
-+
-+ // now the thread can start
-+ this.threads[i].start();
++ if (threadModifier == null) {
++ throw new NullPointerException("Thread factory may not be null");
+ }
+ }
+
-+ /**
-+ * Returns an array representing the threads backing this thread pool.
-+ */
-+ public Thread[] getThreads() {
-+ return Arrays.copyOf(this.threads, this.threads.length, Thread[].class);
++ public Thread[] getAliveThreads() {
++ final PrioritisedThread[] threads = this.aliveThreads.getArray();
++
++ return Arrays.copyOf(threads, threads.length, Thread[].class);
+ }
+
-+ /**
-+ * Creates and returns a {@link PrioritisedPoolExecutor} to schedule tasks onto. The returned executor will execute
-+ * tasks on this thread pool only.
-+ * @param name The debug name of the executor.
-+ * @param minParallelism The minimum number of threads to be executing tasks from the returned executor
-+ * before threads may be allocated to other queues in this thread pool.
-+ * @param parallelism The maximum number of threads which may be executing tasks from the returned executor.
-+ * @throws IllegalStateException If this thread pool is shut down
-+ */
-+ public PrioritisedPoolExecutor createExecutor(final String name, final int minParallelism, final int parallelism) {
-+ synchronized (this.nonShutdownQueues) {
-+ if (this.shutdown) {
-+ throw new IllegalStateException("Queue is shutdown: " + this.toString());
-+ }
-+ final PrioritisedPoolExecutorImpl ret = new PrioritisedPoolExecutorImpl(
-+ this, name,
-+ Math.min(Math.max(1, parallelism), this.threads.length),
-+ Math.min(Math.max(0, minParallelism), this.threads.length)
-+ );
++ public Thread[] getCoreThreads() {
++ final PrioritisedThread[] threads = this.threads.getArray();
+
-+ this.nonShutdownQueues.add(ret);
-+
-+ synchronized (this.activeQueues) {
-+ this.activeQueues.add(ret);
-+ }
-+
-+ return ret;
-+ }
++ return Arrays.copyOf(threads, threads.length, Thread[].class);
+ }
+
+ /**
+ * Prevents creation of new queues, shutdowns all non-shutdown queues if specified
+ */
+ public void halt(final boolean shutdownQueues) {
-+ synchronized (this.nonShutdownQueues) {
++ synchronized (this) {
+ this.shutdown = true;
+ }
-+ if (shutdownQueues) {
-+ final ArrayList queuesToShutdown;
-+ synchronized (this.nonShutdownQueues) {
-+ this.shutdown = true;
-+ queuesToShutdown = new ArrayList<>(this.nonShutdownQueues);
-+ }
+
-+ for (final PrioritisedPoolExecutorImpl queue : queuesToShutdown) {
-+ queue.shutdown();
++ if (shutdownQueues) {
++ for (final ExecutorGroup group : this.executors.getArray()) {
++ for (final ExecutorGroup.ThreadPoolExecutor executor : group.executors.getArray()) {
++ executor.shutdown();
++ }
+ }
+ }
+
-+
-+ for (final PrioritisedThread thread : this.threads) {
-+ // can't kill queue, queue is null
++ for (final PrioritisedThread thread : this.threads.getArray()) {
+ thread.halt(false);
+ }
+ }
@@ -2886,18 +3560,18 @@ index 0000000000000000000000000000000000000000..2ba36e29d0d8693f2f5e6c6d195ca27f
+ final long deadline = start + nsToWait;
+ boolean interrupted = false;
+ try {
-+ for (final PrioritisedThread thread : this.threads) {
++ for (final PrioritisedThread thread : this.aliveThreads.getArray()) {
+ for (;;) {
+ if (!thread.isAlive()) {
+ break;
+ }
+ final long current = System.nanoTime();
-+ if (current >= deadline) {
++ if (current >= deadline && msToWait > 0L) {
+ return false;
+ }
+
+ try {
-+ thread.join(Math.max(1L, (deadline - current) / (1000 * 1000)));
++ thread.join(msToWait <= 0L ? 0L : Math.max(1L, (deadline - current) / (1000 * 1000)));
+ } catch (final InterruptedException ex) {
+ if (interruptable) {
+ throw ex;
@@ -2917,45 +3591,189 @@ index 0000000000000000000000000000000000000000..2ba36e29d0d8693f2f5e6c6d195ca27f
+
+ /**
+ * Shuts down this thread pool, optionally waiting for all tasks to be executed.
-+ * This function will invoke {@link PrioritisedPoolExecutor#shutdown()} on all created executors on this
++ * This function will invoke {@link PrioritisedExecutor#shutdown()} on all created executors on this
+ * thread pool.
+ * @param wait Whether to wait for tasks to be executed
+ */
+ public void shutdown(final boolean wait) {
-+ final ArrayList queuesToShutdown;
-+ synchronized (this.nonShutdownQueues) {
++ synchronized (this) {
+ this.shutdown = true;
-+ queuesToShutdown = new ArrayList<>(this.nonShutdownQueues);
+ }
+
-+ for (final PrioritisedPoolExecutorImpl queue : queuesToShutdown) {
-+ queue.shutdown();
++ for (final ExecutorGroup group : this.executors.getArray()) {
++ for (final ExecutorGroup.ThreadPoolExecutor executor : group.executors.getArray()) {
++ executor.shutdown();
++ }
+ }
+
-+ for (final PrioritisedThread thread : this.threads) {
++
++ for (final PrioritisedThread thread : this.threads.getArray()) {
+ // none of these can be true or else NPE
+ thread.close(false, false);
+ }
+
+ if (wait) {
-+ final ArrayList queues;
-+ synchronized (this.activeQueues) {
-+ queues = new ArrayList<>(this.activeQueues);
++ this.join(0L);
++ }
++ }
++
++ private void die(final PrioritisedThread thread) {
++ this.aliveThreads.remove(thread);
++ }
++
++ public void adjustThreadCount(final int threads) {
++ synchronized (this) {
++ if (this.shutdown) {
++ return;
+ }
-+ for (final PrioritisedPoolExecutorImpl queue : queues) {
-+ queue.waitUntilAllExecuted();
++
++ final PrioritisedThread[] currentThreads = this.threads.getArray();
++ if (threads == currentThreads.length) {
++ // no adjustment needed
++ return;
++ }
++
++ if (threads < currentThreads.length) {
++ // we need to trim threads
++ for (int i = 0, difference = currentThreads.length - threads; i < difference; ++i) {
++ final PrioritisedThread remove = currentThreads[currentThreads.length - i - 1];
++
++ remove.halt(false);
++ this.threads.remove(remove);
++ }
++ } else {
++ // we need to add threads
++ for (int i = 0, difference = threads - currentThreads.length; i < difference; ++i) {
++ final PrioritisedThread thread = new PrioritisedThread();
++
++ this.threadModifier.accept(thread);
++ this.aliveThreads.add(thread);
++ this.threads.add(thread);
++
++ thread.start();
++ }
+ }
+ }
+ }
+
-+ protected static final class PrioritisedThread extends PrioritisedQueueExecutorThread {
++ private static int compareInsideGroup(final ExecutorGroup.ThreadPoolExecutor src, final Priority srcPriority,
++ final ExecutorGroup.ThreadPoolExecutor dst, final Priority dstPriority) {
++ final int priorityCompare = srcPriority.ordinal() - dstPriority.ordinal();
++ if (priorityCompare != 0) {
++ return priorityCompare;
++ }
+
-+ protected final PrioritisedThreadPool pool;
-+ protected final AtomicBoolean alertedHighPriority = new AtomicBoolean();
++ final int parallelismCompare = src.currentParallelism - dst.currentParallelism;
++ if (parallelismCompare != 0) {
++ return parallelismCompare;
++ }
+
-+ public PrioritisedThread(final PrioritisedThreadPool pool) {
++ return TimeUtil.compareTimes(src.lastRetrieved, dst.lastRetrieved);
++ }
++
++ private static int compareOutsideGroup(final ExecutorGroup.ThreadPoolExecutor src, final Priority srcPriority,
++ final ExecutorGroup.ThreadPoolExecutor dst, final Priority dstPriority) {
++ if (src.getGroup().division == dst.getGroup().division) {
++ // can only compare priorities inside the same division
++ final int priorityCompare = srcPriority.ordinal() - dstPriority.ordinal();
++ if (priorityCompare != 0) {
++ return priorityCompare;
++ }
++ }
++
++ final int parallelismCompare = src.getGroup().currentParallelism - dst.getGroup().currentParallelism;
++ if (parallelismCompare != 0) {
++ return parallelismCompare;
++ }
++
++ return TimeUtil.compareTimes(src.lastRetrieved, dst.lastRetrieved);
++ }
++
++ private ExecutorGroup.ThreadPoolExecutor obtainQueue() {
++ final long time = System.nanoTime();
++ synchronized (this) {
++ ExecutorGroup.ThreadPoolExecutor ret = null;
++ Priority retPriority = null;
++
++ for (final ExecutorGroup executorGroup : this.executors.getArray()) {
++ ExecutorGroup.ThreadPoolExecutor highest = null;
++ Priority highestPriority = null;
++ for (final ExecutorGroup.ThreadPoolExecutor executor : executorGroup.executors.getArray()) {
++ final int maxParallelism = executor.maxParallelism;
++ if (maxParallelism > 0 && executor.currentParallelism >= maxParallelism) {
++ continue;
++ }
++
++ final Priority priority = executor.getTargetPriority();
++
++ if (priority == null) {
++ continue;
++ }
++
++ if (highestPriority == null || compareInsideGroup(highest, highestPriority, executor, priority) > 0) {
++ highest = executor;
++ highestPriority = priority;
++ }
++ }
++
++ if (highest == null) {
++ continue;
++ }
++
++ if (ret == null || compareOutsideGroup(ret, retPriority, highest, highestPriority) > 0) {
++ ret = highest;
++ retPriority = highestPriority;
++ }
++ }
++
++ if (ret != null) {
++ ret.lastRetrieved = time;
++ ++ret.currentParallelism;
++ ++ret.getGroup().currentParallelism;
++ return ret;
++ }
++
++ return ret;
++ }
++ }
++
++ private void returnQueue(final ExecutorGroup.ThreadPoolExecutor executor) {
++ synchronized (this) {
++ --executor.currentParallelism;
++ --executor.getGroup().currentParallelism;
++ }
++
++ if (executor.isShutdown() && executor.queue.hasNoScheduledTasks()) {
++ executor.getGroup().executors.remove(executor);
++ }
++ }
++
++ private void notifyAllThreads() {
++ for (final PrioritisedThread thread : this.threads.getArray()) {
++ thread.notifyTasks();
++ }
++ }
++
++ public ExecutorGroup createExecutorGroup(final int division, final int flags) {
++ synchronized (this) {
++ if (this.shutdown) {
++ throw new IllegalStateException("Queue is shutdown: " + this.toString());
++ }
++
++ final ExecutorGroup ret = new ExecutorGroup(division, flags);
++
++ this.executors.add(ret);
++
++ return ret;
++ }
++ }
++
++ private final class PrioritisedThread extends PrioritisedQueueExecutorThread {
++
++ private final AtomicBoolean alertedHighPriority = new AtomicBoolean();
++
++ public PrioritisedThread() {
+ super(null);
-+ this.pool = pool;
+ }
+
+ public boolean alertHighPriorityExecutor() {
@@ -2974,753 +3792,413 @@ index 0000000000000000000000000000000000000000..2ba36e29d0d8693f2f5e6c6d195ca27f
+ }
+
+ @Override
-+ protected boolean pollTasks() {
-+ final PrioritisedThreadPool pool = this.pool;
-+ final TreeSet queues = this.pool.queues;
++ protected void die() {
++ PrioritisedThreadPool.this.die(this);
++ }
+
++ @Override
++ protected boolean pollTasks() {
+ boolean ret = false;
++
+ for (;;) {
+ if (this.halted) {
+ break;
+ }
-+ // try to find a queue
-+ // note that if and ONLY IF the queues set is empty, this means there are no tasks for us to execute.
-+ // so we can only break when it's empty
-+ final PrioritisedPoolExecutorImpl queue;
-+ // select queue
-+ synchronized (queues) {
-+ queue = queues.pollFirst();
-+ if (queue == null) {
-+ // no tasks to execute
-+ break;
-+ }
+
-+ queue.schedulingId = ++pool.schedulingIdGenerator;
-+ // we own this queue now, so increment the executor count
-+ // do we also need to push this queue up for grabs for another executor?
-+ if (++queue.concurrentExecutors < queue.maximumExecutors) {
-+ // re-add to queues
-+ // it's very important this is done in the same synchronised block for polling, as this prevents
-+ // us from possibly later adding a queue that should not exist in the set
-+ queues.add(queue);
-+ queue.isQueued = true;
-+ } else {
-+ queue.isQueued = false;
-+ }
-+ // note: we cannot drain entries from the queue while holding this lock, as it will cause deadlock
-+ // the queue addition holds the per-queue lock first then acquires the lock we have now, but if we
-+ // try to poll now we don't hold the per queue lock but we do hold the global lock...
++ final ExecutorGroup.ThreadPoolExecutor executor = PrioritisedThreadPool.this.obtainQueue();
++ if (executor == null) {
++ break;
+ }
-+
-+ // parse tasks as long as we are allowed
-+ final long start = System.nanoTime();
-+ final long deadline = start + pool.queueMaxHoldTime;
++ final long deadline = System.nanoTime() + executor.queueMaxHoldTime;
+ do {
+ try {
-+ if (this.halted) {
++ if (this.halted || executor.halt) {
+ break;
+ }
-+ if (!queue.executeTask()) {
++ if (!executor.executeTask()) {
+ // no more tasks, try next queue
+ break;
+ }
+ ret = true;
-+ } catch (final ThreadDeath death) {
-+ throw death; // goodbye world...
+ } catch (final Throwable throwable) {
-+ LOGGER.error("Exception thrown from thread '" + this.getName() + "' in queue '" + queue.toString() + "'", throwable);
++ LOGGER.error("Exception thrown from thread '" + this.getName() + "' in queue '" + executor.toString() + "'", throwable);
+ }
+ } while (!this.isAlertedHighPriority() && System.nanoTime() <= deadline);
+
-+ synchronized (queues) {
-+ // decrement executors, we are no longer executing
-+ if (queue.isQueued) {
-+ queues.remove(queue);
-+ queue.isQueued = false;
-+ }
-+ if (--queue.concurrentExecutors == 0 && queue.scheduledPriority == null) {
-+ // reset scheduling id once the queue is empty again
-+ // this will ensure empty queues are not prioritised suddenly over active queues once tasks are
-+ // queued
-+ queue.schedulingId = 0L;
-+ }
-+
-+ // ensure the executor is queued for execution again
-+ if (!queue.isHalted && queue.scheduledPriority != null) { // make sure it actually has tasks
-+ queues.add(queue);
-+ queue.isQueued = true;
-+ }
-+ }
++ PrioritisedThreadPool.this.returnQueue(executor);
+ }
+
++
+ return ret;
+ }
+ }
+
-+ public interface PrioritisedPoolExecutor extends PrioritisedExecutor {
++ public final class ExecutorGroup {
+
-+ /**
-+ * Removes this queue from the thread pool without shutting the queue down or waiting for queued tasks to be executed
-+ */
-+ public void halt();
++ private final AtomicLong subOrderGenerator = new AtomicLong();
++ private final COWArrayList executors = new COWArrayList<>(ThreadPoolExecutor.class);
+
-+ /**
-+ * Returns whether this executor is scheduled to run tasks or is running tasks, otherwise it returns whether
-+ * this queue is not halted and not shutdown.
-+ */
-+ public boolean isActive();
-+ }
++ private final int division;
++ private int currentParallelism;
+
-+ protected static final class PrioritisedPoolExecutorImpl extends PrioritisedThreadedTaskQueue implements PrioritisedPoolExecutor {
-+
-+ protected final PrioritisedThreadPool pool;
-+ protected final long[] priorityCounts = new long[Priority.TOTAL_SCHEDULABLE_PRIORITIES];
-+ protected long schedulingId;
-+ protected int concurrentExecutors;
-+ protected Priority scheduledPriority;
-+
-+ protected final String name;
-+ protected final int maximumExecutors;
-+ protected final int minimumExecutors;
-+ protected boolean isQueued;
-+
-+ public PrioritisedPoolExecutorImpl(final PrioritisedThreadPool pool, final String name, final int maximumExecutors, final int minimumExecutors) {
-+ this.pool = pool;
-+ this.name = name;
-+ this.maximumExecutors = maximumExecutors;
-+ this.minimumExecutors = minimumExecutors;
++ private ExecutorGroup(final int division, final int flags) {
++ this.division = division;
+ }
+
-+ public static Comparator comparator() {
-+ return (final PrioritisedPoolExecutorImpl p1, final PrioritisedPoolExecutorImpl p2) -> {
-+ if (p1 == p2) {
-+ return 0;
-+ }
-+
-+ final int belowMin1 = p1.minimumExecutors - p1.concurrentExecutors;
-+ final int belowMin2 = p2.minimumExecutors - p2.concurrentExecutors;
-+
-+ // test minimum executors
-+ if (belowMin1 > 0 || belowMin2 > 0) {
-+ // want the largest belowMin to be first
-+ final int minCompare = Integer.compare(belowMin2, belowMin1);
-+
-+ if (minCompare != 0) {
-+ return minCompare;
-+ }
-+ }
-+
-+ // prefer higher priority
-+ final int priorityCompare = p1.scheduledPriority.ordinal() - p2.scheduledPriority.ordinal();
-+ if (priorityCompare != 0) {
-+ return priorityCompare;
-+ }
-+
-+ // try to spread out the executors so that each can have threads executing
-+ final int executorCompare = p1.concurrentExecutors - p2.concurrentExecutors;
-+ if (executorCompare != 0) {
-+ return executorCompare;
-+ }
-+
-+ // if all else fails here we just choose whichever executor was queued first
-+ return Long.compare(p1.schedulingId, p2.schedulingId);
-+ };
++ public ThreadPoolExecutor[] getAllExecutors() {
++ return this.executors.getArray().clone();
+ }
+
-+ private boolean isHalted;
++ private PrioritisedThreadPool getThreadPool() {
++ return PrioritisedThreadPool.this;
++ }
+
-+ @Override
-+ public void halt() {
-+ final PrioritisedThreadPool pool = this.pool;
-+ final TreeSet queues = pool.queues;
-+ synchronized (queues) {
-+ if (this.isHalted) {
++ public ThreadPoolExecutor createExecutor(final int maxParallelism, final long queueMaxHoldTime, final int flags) {
++ synchronized (PrioritisedThreadPool.this) {
++ if (PrioritisedThreadPool.this.shutdown) {
++ throw new IllegalStateException("Queue is shutdown: " + PrioritisedThreadPool.this.toString());
++ }
++
++ final ThreadPoolExecutor ret = new ThreadPoolExecutor(maxParallelism, queueMaxHoldTime, flags);
++
++ this.executors.add(ret);
++
++ return ret;
++ }
++ }
++
++ public final class ThreadPoolExecutor implements PrioritisedExecutor {
++
++ private final PrioritisedTaskQueue queue = new PrioritisedTaskQueue();
++
++ private volatile int maxParallelism;
++ private final long queueMaxHoldTime;
++ private volatile int currentParallelism;
++ private volatile boolean halt;
++ private long lastRetrieved = System.nanoTime();
++
++ private ThreadPoolExecutor(final int maxParallelism, final long queueMaxHoldTime, final int flags) {
++ this.maxParallelism = maxParallelism;
++ this.queueMaxHoldTime = queueMaxHoldTime;
++ }
++
++ private ExecutorGroup getGroup() {
++ return ExecutorGroup.this;
++ }
++
++ private boolean canNotify() {
++ if (this.halt) {
++ return false;
++ }
++
++ final int max = this.maxParallelism;
++ return max < 0 || this.currentParallelism < max;
++ }
++
++ private void notifyHighPriority() {
++ if (!this.canNotify()) {
+ return;
+ }
-+ this.isHalted = true;
-+ if (this.isQueued) {
-+ queues.remove(this);
-+ this.isQueued = false;
++ for (final PrioritisedThread thread : this.getGroup().getThreadPool().threads.getArray()) {
++ if (thread.alertHighPriorityExecutor()) {
++ return;
++ }
+ }
+ }
-+ synchronized (pool.nonShutdownQueues) {
-+ pool.nonShutdownQueues.remove(this);
-+ }
-+ synchronized (pool.activeQueues) {
-+ pool.activeQueues.remove(this);
-+ }
-+ }
+
-+ @Override
-+ public boolean isActive() {
-+ final PrioritisedThreadPool pool = this.pool;
-+ final TreeSet queues = pool.queues;
-+
-+ synchronized (queues) {
-+ if (this.concurrentExecutors != 0) {
-+ return true;
++ private void notifyScheduled() {
++ if (!this.canNotify()) {
++ return;
+ }
-+ synchronized (pool.activeQueues) {
-+ if (pool.activeQueues.contains(this)) {
++ for (final PrioritisedThread thread : this.getGroup().getThreadPool().threads.getArray()) {
++ if (thread.notifyTasks()) {
++ return;
++ }
++ }
++ }
++
++ /**
++ * Removes this queue from the thread pool without shutting the queue down or waiting for queued tasks to be executed
++ */
++ public void halt() {
++ this.halt = true;
++
++ ExecutorGroup.this.executors.remove(this);
++ }
++
++ /**
++ * Returns whether this executor is scheduled to run tasks or is running tasks, otherwise it returns whether
++ * this queue is not halted and not shutdown.
++ */
++ public boolean isActive() {
++ if (this.halt) {
++ return this.currentParallelism > 0;
++ } else {
++ if (!this.isShutdown()) {
+ return true;
+ }
++
++ return !this.queue.hasNoScheduledTasks();
+ }
+ }
+
-+ return false;
-+ }
-+
-+ private long totalQueuedTasks = 0L;
-+
-+ @Override
-+ protected void priorityChange(final PrioritisedThreadedTaskQueue.PrioritisedTask task, final Priority from, final Priority to) {
-+ // Note: The superclass' queue lock is ALWAYS held when inside this method. So we do NOT need to do any additional synchronisation
-+ // for accessing this queue's state.
-+ final long[] priorityCounts = this.priorityCounts;
-+ final boolean shutdown = this.isShutdown();
-+
-+ if (from == null && to == Priority.COMPLETING) {
-+ throw new IllegalStateException("Cannot complete task without queueing it first");
-+ }
-+
-+ // we should only notify for queueing of tasks, not changing priorities
-+ final boolean shouldNotifyTasks = from == null;
-+
-+ final Priority scheduledPriority = this.scheduledPriority;
-+ if (from != null) {
-+ --priorityCounts[from.priority];
-+ }
-+ if (to != Priority.COMPLETING) {
-+ ++priorityCounts[to.priority];
-+ }
-+ final long totalQueuedTasks;
-+ if (to == Priority.COMPLETING) {
-+ totalQueuedTasks = --this.totalQueuedTasks;
-+ } else if (from == null) {
-+ totalQueuedTasks = ++this.totalQueuedTasks;
-+ } else {
-+ totalQueuedTasks = this.totalQueuedTasks;
-+ }
-+
-+ // find new highest priority
-+ int highest = Math.min(to == Priority.COMPLETING ? Priority.IDLE.priority : to.priority, scheduledPriority == null ? Priority.IDLE.priority : scheduledPriority.priority);
-+ int lowestPriority = priorityCounts.length; // exclusive
-+ for (;highest < lowestPriority; ++highest) {
-+ final long count = priorityCounts[highest];
-+ if (count < 0) {
-+ throw new IllegalStateException("Priority " + highest + " has " + count + " scheduled tasks");
++ @Override
++ public boolean shutdown() {
++ if (!this.queue.shutdown()) {
++ return false;
+ }
+
-+ if (count != 0) {
-+ break;
++ if (this.queue.hasNoScheduledTasks()) {
++ ExecutorGroup.this.executors.remove(this);
++ }
++
++ return true;
++ }
++
++ @Override
++ public boolean isShutdown() {
++ return this.queue.isShutdown();
++ }
++
++ public void setMaxParallelism(final int maxParallelism) {
++ this.maxParallelism = maxParallelism;
++ // assume that we could have increased the parallelism
++ if (this.getTargetPriority() != null) {
++ ExecutorGroup.this.getThreadPool().notifyAllThreads();
+ }
+ }
+
-+ final Priority newPriority;
-+ if (highest == lowestPriority) {
-+ // no tasks left
-+ newPriority = null;
-+ } else if (shutdown) {
-+ // whichever is lower, the actual greatest priority or simply HIGHEST
-+ // this is so shutdown automatically gets priority
-+ newPriority = Priority.getPriority(Math.min(highest, Priority.HIGHEST.priority));
-+ } else {
-+ newPriority = Priority.getPriority(highest);
-+ }
-+
-+ final int executorsWanted;
-+ boolean shouldNotifyHighPriority = false;
-+
-+ final PrioritisedThreadPool pool = this.pool;
-+ final TreeSet queues = pool.queues;
-+
-+ synchronized (queues) {
-+ if (!this.isQueued) {
-+ // see if we need to be queued
-+ if (newPriority != null) {
-+ if (this.schedulingId == 0L) {
-+ this.schedulingId = ++pool.schedulingIdGenerator;
-+ }
-+ this.scheduledPriority = newPriority; // must be updated before queue add
-+ if (!this.isHalted && this.concurrentExecutors < this.maximumExecutors) {
-+ shouldNotifyHighPriority = newPriority.isHigherOrEqualPriority(Priority.HIGH);
-+ queues.add(this);
-+ this.isQueued = true;
-+ }
-+ } else {
-+ // do not queue
-+ this.scheduledPriority = null;
-+ }
-+ } else {
-+ // see if we need to NOT be queued
-+ if (newPriority == null) {
-+ queues.remove(this);
-+ this.scheduledPriority = null;
-+ this.isQueued = false;
-+ } else if (scheduledPriority != newPriority) {
-+ // if our priority changed, we need to update it - which means removing and re-adding into the queue
-+ queues.remove(this);
-+ // only now can we update scheduledPriority, since we are no longer in queue
-+ this.scheduledPriority = newPriority;
-+ queues.add(this);
-+ shouldNotifyHighPriority = (scheduledPriority == null || scheduledPriority.isLowerPriority(Priority.HIGH)) && newPriority.isHigherOrEqualPriority(Priority.HIGH);
-+ }
++ Priority getTargetPriority() {
++ final Priority ret = this.queue.getHighestPriority();
++ if (!this.isShutdown()) {
++ return ret;
+ }
+
-+ if (this.isQueued) {
-+ executorsWanted = Math.min(this.maximumExecutors - this.concurrentExecutors, (int)totalQueuedTasks);
-+ } else {
-+ executorsWanted = 0;
-+ }
++ return ret == null ? QUEUE_SHUTDOWN_PRIORITY : Priority.max(ret, QUEUE_SHUTDOWN_PRIORITY);
+ }
+
-+ if (newPriority == null && shutdown) {
-+ synchronized (pool.activeQueues) {
-+ pool.activeQueues.remove(this);
-+ }
++ @Override
++ public long getTotalTasksScheduled() {
++ return this.queue.getTotalTasksScheduled();
+ }
+
-+ // Wake up the number of executors we want
-+ if (executorsWanted > 0 || (shouldNotifyTasks | shouldNotifyHighPriority)) {
-+ int notified = 0;
-+ for (final PrioritisedThread thread : pool.threads) {
-+ if ((shouldNotifyHighPriority ? thread.alertHighPriorityExecutor() : thread.notifyTasks())
-+ && (++notified >= executorsWanted)) {
-+ break;
-+ }
-+ }
++ @Override
++ public long getTotalTasksExecuted() {
++ return this.queue.getTotalTasksExecuted();
+ }
-+ }
+
-+ @Override
-+ public boolean shutdown() {
-+ final boolean ret = super.shutdown();
-+ if (!ret) {
++ @Override
++ public long generateNextSubOrder() {
++ return ExecutorGroup.this.subOrderGenerator.getAndIncrement();
++ }
++
++ @Override
++ public boolean executeTask() {
++ return this.queue.executeTask();
++ }
++
++ @Override
++ public PrioritisedTask queueTask(final Runnable task) {
++ final PrioritisedTask ret = this.createTask(task);
++
++ ret.queue();
++
+ return ret;
+ }
+
-+ final PrioritisedThreadPool pool = this.pool;
++ @Override
++ public PrioritisedTask queueTask(final Runnable task, final Priority priority) {
++ final PrioritisedTask ret = this.createTask(task, priority);
+
-+ // remove from active queues
-+ synchronized (pool.nonShutdownQueues) {
-+ pool.nonShutdownQueues.remove(this);
++ ret.queue();
++
++ return ret;
+ }
+
-+ final TreeSet queues = pool.queues;
++ @Override
++ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) {
++ final PrioritisedTask ret = this.createTask(task, priority, subOrder);
+
-+ // try and shift around our priority
-+ synchronized (queues) {
-+ if (this.scheduledPriority == null) {
-+ // no tasks are queued, ensure we aren't in activeQueues
-+ synchronized (pool.activeQueues) {
-+ pool.activeQueues.remove(this);
++ ret.queue();
++
++ return ret;
++ }
++
++ @Override
++ public PrioritisedTask createTask(final Runnable task) {
++ return this.createTask(task, Priority.NORMAL);
++ }
++
++ @Override
++ public PrioritisedTask createTask(final Runnable task, final Priority priority) {
++ return this.createTask(task, priority, this.generateNextSubOrder());
++ }
++
++ @Override
++ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) {
++ return new WrappedTask(this.queue.createTask(task, priority, subOrder));
++ }
++
++ private final class WrappedTask implements PrioritisedTask {
++
++ private final PrioritisedTask wrapped;
++
++ private WrappedTask(final PrioritisedTask wrapped) {
++ this.wrapped = wrapped;
++ }
++
++ @Override
++ public PrioritisedExecutor getExecutor() {
++ return ThreadPoolExecutor.this;
++ }
++
++ @Override
++ public boolean queue() {
++ if (this.wrapped.queue()) {
++ final Priority priority = this.getPriority();
++ if (priority != Priority.COMPLETING) {
++ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) {
++ ThreadPoolExecutor.this.notifyHighPriority();
++ } else {
++ ThreadPoolExecutor.this.notifyScheduled();
++ }
++ }
++ return true;
+ }
+
-+ return ret;
++ return false;
+ }
+
-+ // try to set scheduled priority to HIGHEST so it drains faster
-+
-+ if (this.scheduledPriority.isHigherOrEqualPriority(Priority.HIGHEST)) {
-+ // already at target priority (highest or above)
-+ return ret;
++ @Override
++ public boolean isQueued() {
++ return this.wrapped.isQueued();
+ }
+
-+ // shift priority to HIGHEST
++ @Override
++ public boolean cancel() {
++ return this.wrapped.cancel();
++ }
+
-+ if (this.isQueued) {
-+ queues.remove(this);
-+ this.scheduledPriority = Priority.HIGHEST;
-+ queues.add(this);
-+ } else {
-+ this.scheduledPriority = Priority.HIGHEST;
++ @Override
++ public boolean execute() {
++ return this.wrapped.execute();
++ }
++
++ @Override
++ public Priority getPriority() {
++ return this.wrapped.getPriority();
++ }
++
++ @Override
++ public boolean setPriority(final Priority priority) {
++ if (this.wrapped.setPriority(priority)) {
++ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) {
++ ThreadPoolExecutor.this.notifyHighPriority();
++ }
++ return true;
++ }
++
++ return false;
++ }
++
++ @Override
++ public boolean raisePriority(final Priority priority) {
++ if (this.wrapped.raisePriority(priority)) {
++ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) {
++ ThreadPoolExecutor.this.notifyHighPriority();
++ }
++ return true;
++ }
++
++ return false;
++ }
++
++ @Override
++ public boolean lowerPriority(final Priority priority) {
++ return this.wrapped.lowerPriority(priority);
++ }
++
++ @Override
++ public long getSubOrder() {
++ return this.wrapped.getSubOrder();
++ }
++
++ @Override
++ public boolean setSubOrder(final long subOrder) {
++ return this.wrapped.setSubOrder(subOrder);
++ }
++
++ @Override
++ public boolean raiseSubOrder(final long subOrder) {
++ return this.wrapped.raiseSubOrder(subOrder);
++ }
++
++ @Override
++ public boolean lowerSubOrder(final long subOrder) {
++ return this.wrapped.lowerSubOrder(subOrder);
++ }
++
++ @Override
++ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) {
++ if (this.wrapped.setPriorityAndSubOrder(priority, subOrder)) {
++ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) {
++ ThreadPoolExecutor.this.notifyHighPriority();
++ }
++ return true;
++ }
++
++ return false;
+ }
+ }
-+
-+ return ret;
-+ }
-+ }
-+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..3e8401b1b1f833c4f01bc87059a2f48d761d989f
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java
-@@ -0,0 +1,378 @@
-+package ca.spottedleaf.concurrentutil.executor.standard;
-+
-+import java.util.ArrayDeque;
-+import java.util.concurrent.atomic.AtomicLong;
-+
-+public class PrioritisedThreadedTaskQueue implements PrioritisedExecutor {
-+
-+ protected final ArrayDeque[] queues = new ArrayDeque[Priority.TOTAL_SCHEDULABLE_PRIORITIES]; {
-+ for (int i = 0; i < Priority.TOTAL_SCHEDULABLE_PRIORITIES; ++i) {
-+ this.queues[i] = new ArrayDeque<>();
+ }
+ }
+
-+ // Use AtomicLong to separate from the queue field, we don't want false sharing here.
-+ protected final AtomicLong totalScheduledTasks = new AtomicLong();
-+ protected final AtomicLong totalCompletedTasks = new AtomicLong();
++ private static final class COWArrayList {
+
-+ // this is here to prevent failures to queue stalling flush() calls (as the schedule calls would increment totalScheduledTasks without this check)
-+ protected volatile boolean hasShutdown;
++ private volatile E[] array;
+
-+ protected long taskIdGenerator = 0;
-+
-+ @Override
-+ public PrioritisedExecutor.PrioritisedTask queueRunnable(final Runnable task, final Priority priority) throws IllegalStateException, IllegalArgumentException {
-+ if (!Priority.isValidPriority(priority)) {
-+ throw new IllegalArgumentException("Priority " + priority + " is invalid");
-+ }
-+ if (task == null) {
-+ throw new NullPointerException("Task cannot be null");
++ public COWArrayList(final Class clazz) {
++ this.array = (E[])Array.newInstance(clazz, 0);
+ }
+
-+ if (this.hasShutdown) {
-+ // prevent us from stalling flush() calls by incrementing scheduled tasks when we really didn't schedule something
-+ throw new IllegalStateException("Queue has shutdown");
++ public E[] getArray() {
++ return this.array;
+ }
+
-+ final PrioritisedTask ret;
++ public void add(final E element) {
++ synchronized (this) {
++ final E[] array = this.array;
+
-+ synchronized (this.queues) {
-+ if (this.hasShutdown) {
-+ throw new IllegalStateException("Queue has shutdown");
++ final E[] copy = Arrays.copyOf(array, array.length + 1);
++ copy[array.length] = element;
++
++ this.array = copy;
+ }
-+ this.getAndAddTotalScheduledTasksVolatile(1L);
-+
-+ ret = new PrioritisedTask(this.taskIdGenerator++, task, priority, this);
-+
-+ this.queues[ret.priority.priority].add(ret);
-+
-+ // call priority change callback (note: only after we successfully queue!)
-+ this.priorityChange(ret, null, priority);
+ }
+
-+ return ret;
-+ }
-+
-+ @Override
-+ public PrioritisedExecutor.PrioritisedTask createTask(final Runnable task, final Priority priority) {
-+ if (!Priority.isValidPriority(priority)) {
-+ throw new IllegalArgumentException("Priority " + priority + " is invalid");
-+ }
-+ if (task == null) {
-+ throw new NullPointerException("Task cannot be null");
-+ }
-+
-+ return new PrioritisedTask(task, priority, this);
-+ }
-+
-+ @Override
-+ public long getTotalTasksScheduled() {
-+ return this.totalScheduledTasks.get();
-+ }
-+
-+ @Override
-+ public long getTotalTasksExecuted() {
-+ return this.totalCompletedTasks.get();
-+ }
-+
-+ // callback method for subclasses to override
-+ // from is null when a task is immediately created
-+ protected void priorityChange(final PrioritisedTask task, final Priority from, final Priority to) {}
-+
-+ /**
-+ * Polls the highest priority task currently available. {@code null} if none. This will mark the
-+ * returned task as completed.
-+ */
-+ protected PrioritisedTask poll() {
-+ return this.poll(Priority.IDLE);
-+ }
-+
-+ protected PrioritisedTask poll(final Priority minPriority) {
-+ final ArrayDeque[] queues = this.queues;
-+ synchronized (queues) {
-+ final int max = minPriority.priority;
-+ for (int i = 0; i <= max; ++i) {
-+ final ArrayDeque queue = queues[i];
-+ PrioritisedTask task;
-+ while ((task = queue.pollFirst()) != null) {
-+ if (task.trySetCompleting(i)) {
-+ return task;
++ public boolean remove(final E element) {
++ synchronized (this) {
++ final E[] array = this.array;
++ int index = -1;
++ for (int i = 0, len = array.length; i < len; ++i) {
++ if (array[i] == element) {
++ index = i;
++ break;
+ }
+ }
-+ }
-+ }
+
-+ return null;
-+ }
-+
-+ /**
-+ * Polls and executes the highest priority task currently available. Exceptions thrown during task execution will
-+ * be rethrown.
-+ * @return {@code true} if a task was executed, {@code false} otherwise.
-+ */
-+ @Override
-+ public boolean executeTask() {
-+ final PrioritisedTask task = this.poll();
-+
-+ if (task != null) {
-+ task.executeInternal();
-+ return true;
-+ }
-+
-+ return false;
-+ }
-+
-+ @Override
-+ public boolean shutdown() {
-+ synchronized (this.queues) {
-+ if (this.hasShutdown) {
-+ return false;
-+ }
-+ this.hasShutdown = true;
-+ }
-+ return true;
-+ }
-+
-+ @Override
-+ public boolean isShutdown() {
-+ return this.hasShutdown;
-+ }
-+
-+ /* totalScheduledTasks */
-+
-+ protected final long getTotalScheduledTasksVolatile() {
-+ return this.totalScheduledTasks.get();
-+ }
-+
-+ protected final long getAndAddTotalScheduledTasksVolatile(final long value) {
-+ return this.totalScheduledTasks.getAndAdd(value);
-+ }
-+
-+ /* totalCompletedTasks */
-+
-+ protected final long getTotalCompletedTasksVolatile() {
-+ return this.totalCompletedTasks.get();
-+ }
-+
-+ protected final long getAndAddTotalCompletedTasksVolatile(final long value) {
-+ return this.totalCompletedTasks.getAndAdd(value);
-+ }
-+
-+ protected static final class PrioritisedTask implements PrioritisedExecutor.PrioritisedTask {
-+ protected final PrioritisedThreadedTaskQueue queue;
-+ protected long id;
-+ protected static final long NOT_SCHEDULED_ID = -1L;
-+
-+ protected Runnable runnable;
-+ protected volatile Priority priority;
-+
-+ protected PrioritisedTask(final long id, final Runnable runnable, final Priority priority, final PrioritisedThreadedTaskQueue queue) {
-+ if (!Priority.isValidPriority(priority)) {
-+ throw new IllegalArgumentException("Invalid priority " + priority);
-+ }
-+
-+ this.priority = priority;
-+ this.runnable = runnable;
-+ this.queue = queue;
-+ this.id = id;
-+ }
-+
-+ protected PrioritisedTask(final Runnable runnable, final Priority priority, final PrioritisedThreadedTaskQueue queue) {
-+ if (!Priority.isValidPriority(priority)) {
-+ throw new IllegalArgumentException("Invalid priority " + priority);
-+ }
-+
-+ this.priority = priority;
-+ this.runnable = runnable;
-+ this.queue = queue;
-+ this.id = NOT_SCHEDULED_ID;
-+ }
-+
-+ @Override
-+ public boolean queue() {
-+ if (this.queue.hasShutdown) {
-+ throw new IllegalStateException("Queue has shutdown");
-+ }
-+
-+ synchronized (this.queue.queues) {
-+ if (this.queue.hasShutdown) {
-+ throw new IllegalStateException("Queue has shutdown");
-+ }
-+
-+ final Priority priority = this.priority;
-+ if (priority == Priority.COMPLETING) {
++ if (index == -1) {
+ return false;
+ }
+
-+ if (this.id != NOT_SCHEDULED_ID) {
-+ return false;
-+ }
++ final E[] copy = (E[])Array.newInstance(array.getClass().getComponentType(), array.length - 1);
+
-+ this.queue.getAndAddTotalScheduledTasksVolatile(1L);
-+ this.id = this.queue.taskIdGenerator++;
-+ this.queue.queues[priority.priority].add(this);
++ System.arraycopy(array, 0, copy, 0, index);
++ System.arraycopy(array, index + 1, copy, index, (array.length - 1) - index);
+
-+ this.queue.priorityChange(this, null, priority);
-+
-+ return true;
-+ }
-+ }
-+
-+ protected boolean trySetCompleting(final int minPriority) {
-+ final Priority oldPriority = this.priority;
-+ if (oldPriority != Priority.COMPLETING && oldPriority.isHigherOrEqualPriority(minPriority)) {
-+ this.priority = Priority.COMPLETING;
-+ if (this.id != NOT_SCHEDULED_ID) {
-+ this.queue.priorityChange(this, oldPriority, Priority.COMPLETING);
-+ }
-+ return true;
++ this.array = copy;
+ }
+
-+ return false;
-+ }
-+
-+ @Override
-+ public Priority getPriority() {
-+ return this.priority;
-+ }
-+
-+ @Override
-+ public boolean setPriority(final Priority priority) {
-+ if (!Priority.isValidPriority(priority)) {
-+ throw new IllegalArgumentException("Invalid priority " + priority);
-+ }
-+ synchronized (this.queue.queues) {
-+ final Priority curr = this.priority;
-+
-+ if (curr == Priority.COMPLETING) {
-+ return false;
-+ }
-+
-+ if (curr == priority) {
-+ return true;
-+ }
-+
-+ this.priority = priority;
-+ if (this.id != NOT_SCHEDULED_ID) {
-+ this.queue.queues[priority.priority].add(this);
-+
-+ // call priority change callback
-+ this.queue.priorityChange(this, curr, priority);
-+ }
-+ }
-+
-+ return true;
-+ }
-+
-+ @Override
-+ public boolean raisePriority(final Priority priority) {
-+ if (!Priority.isValidPriority(priority)) {
-+ throw new IllegalArgumentException("Invalid priority " + priority);
-+ }
-+
-+ synchronized (this.queue.queues) {
-+ final Priority curr = this.priority;
-+
-+ if (curr == Priority.COMPLETING) {
-+ return false;
-+ }
-+
-+ if (curr.isHigherOrEqualPriority(priority)) {
-+ return true;
-+ }
-+
-+ this.priority = priority;
-+ if (this.id != NOT_SCHEDULED_ID) {
-+ this.queue.queues[priority.priority].add(this);
-+
-+ // call priority change callback
-+ this.queue.priorityChange(this, curr, priority);
-+ }
-+ }
-+
-+ return true;
-+ }
-+
-+ @Override
-+ public boolean lowerPriority(final Priority priority) {
-+ if (!Priority.isValidPriority(priority)) {
-+ throw new IllegalArgumentException("Invalid priority " + priority);
-+ }
-+
-+ synchronized (this.queue.queues) {
-+ final Priority curr = this.priority;
-+
-+ if (curr == Priority.COMPLETING) {
-+ return false;
-+ }
-+
-+ if (curr.isLowerOrEqualPriority(priority)) {
-+ return true;
-+ }
-+
-+ this.priority = priority;
-+ if (this.id != NOT_SCHEDULED_ID) {
-+ this.queue.queues[priority.priority].add(this);
-+
-+ // call priority change callback
-+ this.queue.priorityChange(this, curr, priority);
-+ }
-+ }
-+
-+ return true;
-+ }
-+
-+ @Override
-+ public boolean cancel() {
-+ final long id;
-+ synchronized (this.queue.queues) {
-+ final Priority oldPriority = this.priority;
-+ if (oldPriority == Priority.COMPLETING) {
-+ return false;
-+ }
-+
-+ this.priority = Priority.COMPLETING;
-+ // call priority change callback
-+ if ((id = this.id) != NOT_SCHEDULED_ID) {
-+ this.queue.priorityChange(this, oldPriority, Priority.COMPLETING);
-+ }
-+ }
-+ this.runnable = null;
-+ if (id != NOT_SCHEDULED_ID) {
-+ this.queue.getAndAddTotalCompletedTasksVolatile(1L);
-+ }
-+ return true;
-+ }
-+
-+ protected void executeInternal() {
-+ try {
-+ final Runnable execute = this.runnable;
-+ this.runnable = null;
-+ execute.run();
-+ } finally {
-+ if (this.id != NOT_SCHEDULED_ID) {
-+ this.queue.getAndAddTotalCompletedTasksVolatile(1L);
-+ }
-+ }
-+ }
-+
-+ @Override
-+ public boolean execute() {
-+ synchronized (this.queue.queues) {
-+ final Priority oldPriority = this.priority;
-+ if (oldPriority == Priority.COMPLETING) {
-+ return false;
-+ }
-+
-+ this.priority = Priority.COMPLETING;
-+ // call priority change callback
-+ if (this.id != NOT_SCHEDULED_ID) {
-+ this.queue.priorityChange(this, oldPriority, Priority.COMPLETING);
-+ }
-+ }
-+
-+ this.executeInternal();
+ return true;
+ }
+ }
@@ -4111,10 +4589,10 @@ index 0000000000000000000000000000000000000000..7ffe4379b06c03c56abbcbdee3bb7208
+}
diff --git a/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java b/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java
new file mode 100644
-index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3cc0bc4090
+index 0000000000000000000000000000000000000000..6918f130099e6c19e20a47bfdb54915cdd13732a
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java
-@@ -0,0 +1,1684 @@
+@@ -0,0 +1,1704 @@
+package ca.spottedleaf.concurrentutil.map;
+
+import ca.spottedleaf.concurrentutil.function.BiLong1Function;
@@ -4160,7 +4638,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+ * @param
+ * @see java.util.concurrent.ConcurrentMap
+ */
-+public class ConcurrentLong2ReferenceChainedHashTable {
++public class ConcurrentLong2ReferenceChainedHashTable implements Iterable> {
+
+ protected static final int DEFAULT_CAPACITY = 16;
+ protected static final float DEFAULT_LOAD_FACTOR = 0.75f;
@@ -4309,6 +4787,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+ }
+
+ if (node.resize) {
++ // noinspection unchecked
+ table = (TableEntry[])node.getValuePlain();
+ continue;
+ }
@@ -4391,10 +4870,10 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+ public int size() {
+ final long ret = this.size.sum();
+
-+ if (ret <= 0L) {
++ if (ret < 0L) {
+ return 0;
+ }
-+ if (ret >= (long)Integer.MAX_VALUE) {
++ if (ret > (long)Integer.MAX_VALUE) {
+ return Integer.MAX_VALUE;
+ }
+
@@ -4458,6 +4937,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+
+ // create new table data
+
++ // noinspection unchecked
+ final TableEntry[] newTable = new TableEntry[capacity];
+ // noinspection unchecked
+ final TableEntry resizeNode = new TableEntry<>(0L, (V)newTable, true);
@@ -4482,6 +4962,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+ throw new IllegalStateException("Resizing to same size");
+ }
+
++ // noinspection unchecked
+ final TableEntry[] work = new TableEntry[1 << capDiffShift]; // typically, capDiffShift = 1
+
+ for (int i = 0, len = oldTable.length; i < len; ++i) {
@@ -4655,6 +5136,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+ }
+
+ if (node.resize) {
++ // noinspection unchecked
+ table = (TableEntry[])node.getValuePlain();
+ continue table_loop;
+ }
@@ -4716,6 +5198,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+ }
+
+ if (node.resize) {
++ // noinspection unchecked
+ table = (TableEntry[])node.getValuePlain();
+ continue table_loop;
+ }
@@ -4770,6 +5253,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+ }
+
+ if (node.resize) {
++ // noinspection unchecked
+ table = (TableEntry[])node.getValuePlain();
+ continue table_loop;
+ }
@@ -4821,6 +5305,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+ }
+
+ if (node.resize) {
++ // noinspection unchecked
+ table = (TableEntry[])node.getValuePlain();
+ continue table_loop;
+ }
@@ -4889,6 +5374,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+ }
+
+ if (node.resize) {
++ // noinspection unchecked
+ table = (TableEntry[])node.getValuePlain();
+ continue table_loop;
+ }
@@ -4965,6 +5451,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+ }
+
+ if (node.resize) {
++ // noinspection unchecked
+ table = (TableEntry[])node.getValuePlain();
+ continue table_loop;
+ }
@@ -5061,6 +5548,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+ }
+
+ if (node.resize) {
++ // noinspection unchecked
+ table = (TableEntry[])node.getValuePlain();
+ continue table_loop;
+ }
@@ -5175,6 +5663,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+ }
+
+ if (node.resize) {
++ // noinspection unchecked
+ table = (TableEntry[])node.getValuePlain();
+ continue table_loop;
+ }
@@ -5243,6 +5732,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+ }
+
+ if (node.resize) {
++ // noinspection unchecked
+ table = (TableEntry[])node.getValuePlain();
+ continue table_loop;
+ }
@@ -5317,6 +5807,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+ }
+
+ if (node.resize) {
++ // noinspection unchecked
+ table = (TableEntry[])node.getValuePlain();
+ continue table_loop;
+ }
@@ -5405,6 +5896,11 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+ return new EntryIterator<>(this);
+ }
+
++ @Override
++ public final Iterator> iterator() {
++ return this.entryIterator();
++ }
++
+ /**
+ * Returns an iterator over the keys in this map. The iterator is only guaranteed to see keys that were
+ * added before the beginning of this call, but it may see keys added during.
@@ -5423,7 +5919,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+
+ protected static final class EntryIterator extends BaseIteratorImpl> {
+
-+ protected EntryIterator(final ConcurrentLong2ReferenceChainedHashTable map) {
++ public EntryIterator(final ConcurrentLong2ReferenceChainedHashTable map) {
+ super(map);
+ }
+
@@ -5443,7 +5939,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+
+ protected static final class KeyIterator extends BaseIteratorImpl implements PrimitiveIterator.OfLong {
+
-+ protected KeyIterator(final ConcurrentLong2ReferenceChainedHashTable map) {
++ public KeyIterator(final ConcurrentLong2ReferenceChainedHashTable map) {
+ super(map);
+ }
+
@@ -5482,7 +5978,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+
+ protected static final class ValueIterator extends BaseIteratorImpl {
+
-+ protected ValueIterator(final ConcurrentLong2ReferenceChainedHashTable map) {
++ public ValueIterator(final ConcurrentLong2ReferenceChainedHashTable map) {
+ super(map);
+ }
+
@@ -5537,7 +6033,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+
+ @Override
+ public final void remove() {
-+ final TableEntry lastReturned = this.nextToReturn;
++ final TableEntry lastReturned = this.lastReturned;
+ if (lastReturned == null) {
+ throw new NoSuchElementException();
+ }
@@ -5609,6 +6105,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+ final ResizeChain chain = this.resizeChain;
+
+ if (chain == null) {
++ // noinspection unchecked
+ final TableEntry[] nextTable = (TableEntry[])entry.getValuePlain();
+
+ final ResizeChain oldChain = new ResizeChain<>(table, null, null);
@@ -5623,6 +6120,7 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+ } else {
+ ResizeChain currChain = chain.next;
+ if (currChain == null) {
++ // noinspection unchecked
+ final TableEntry[] ret = (TableEntry[])entry.getValuePlain();
+ currChain = new ResizeChain<>(ret, chain, null);
+ chain.next = currChain;
@@ -5703,11 +6201,11 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+
+ protected static final class ResizeChain {
+
-+ protected final TableEntry[] table;
-+ protected final ResizeChain prev;
-+ protected ResizeChain next;
++ public final TableEntry[] table;
++ public final ResizeChain prev;
++ public ResizeChain next;
+
-+ protected ResizeChain(final TableEntry[] table, final ResizeChain prev, final ResizeChain next) {
++ public ResizeChain(final TableEntry[] table, final ResizeChain prev, final ResizeChain next) {
+ this.table = table;
+ this.prev = prev;
+ this.next = next;
@@ -5717,64 +6215,64 @@ index 0000000000000000000000000000000000000000..d701998b376579ec652fb94823befa3c
+
+ public static final class TableEntry {
+
-+ protected static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class);
++ private static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class);
+
-+ protected final boolean resize;
++ private final boolean resize;
+
-+ protected final long key;
++ private final long key;
+
-+ protected volatile V value;
-+ protected static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class);
++ private volatile V value;
++ private static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class);
+
-+ protected final V getValuePlain() {
++ private V getValuePlain() {
+ //noinspection unchecked
+ return (V)VALUE_HANDLE.get(this);
+ }
+
-+ protected final V getValueAcquire() {
++ private V getValueAcquire() {
+ //noinspection unchecked
+ return (V)VALUE_HANDLE.getAcquire(this);
+ }
+
-+ protected final V getValueVolatile() {
++ private V getValueVolatile() {
+ //noinspection unchecked
+ return (V)VALUE_HANDLE.getVolatile(this);
+ }
+
-+ protected final void setValuePlain(final V value) {
++ private void setValuePlain(final V value) {
+ VALUE_HANDLE.set(this, (Object)value);
+ }
+
-+ protected final void setValueRelease(final V value) {
++ private void setValueRelease(final V value) {
+ VALUE_HANDLE.setRelease(this, (Object)value);
+ }
+
-+ protected final void setValueVolatile(final V value) {
++ private void setValueVolatile(final V value) {
+ VALUE_HANDLE.setVolatile(this, (Object)value);
+ }
+
-+ protected volatile TableEntry next;
-+ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class);
++ private volatile TableEntry next;
++ private static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class);
+
-+ protected final TableEntry getNextPlain() {
++ private TableEntry getNextPlain() {
+ //noinspection unchecked
+ return (TableEntry)NEXT_HANDLE.get(this);
+ }
+
-+ protected final TableEntry getNextVolatile() {
++ private TableEntry getNextVolatile() {
+ //noinspection unchecked
+ return (TableEntry)NEXT_HANDLE.getVolatile(this);
+ }
+
-+ protected final void setNextPlain(final TableEntry next) {
++ private void setNextPlain(final TableEntry next) {
+ NEXT_HANDLE.set(this, next);
+ }
+
-+ protected final void setNextRelease(final TableEntry next) {
++ private void setNextRelease(final TableEntry next) {
+ NEXT_HANDLE.setRelease(this, next);
+ }
+
-+ protected final void setNextVolatile(final TableEntry next) {
++ private void setNextVolatile(final TableEntry next) {
+ NEXT_HANDLE.setVolatile(this, next);
+ }
+
@@ -8143,10 +8641,10 @@ index 0000000000000000000000000000000000000000..bb301a9f4e3ac919552eef68afc73569
+}
diff --git a/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java
new file mode 100644
-index 0000000000000000000000000000000000000000..8197ccb1c4e5878dbd8007b5fb514640765ec8e4
+index 0000000000000000000000000000000000000000..85e6ef636d435a0ee4bf3e0760b0c87422c520a1
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java
-@@ -0,0 +1,558 @@
+@@ -0,0 +1,564 @@
+package ca.spottedleaf.concurrentutil.scheduler;
+
+import ca.spottedleaf.concurrentutil.set.LinkedSortedSet;
@@ -8162,6 +8660,10 @@ index 0000000000000000000000000000000000000000..8197ccb1c4e5878dbd8007b5fb514640
+import java.util.concurrent.locks.LockSupport;
+import java.util.function.BooleanSupplier;
+
++/**
++ * @deprecated To be replaced
++ */
++@Deprecated
+public class SchedulerThreadPool {
+
+ public static final long DEADLINE_NOT_SET = Long.MIN_VALUE;
@@ -8446,7 +8948,9 @@ index 0000000000000000000000000000000000000000..8197ccb1c4e5878dbd8007b5fb514640
+ * is invoked for any scheduled task - otherwise, {@link #runTasks(BooleanSupplier)} may not be invoked to
+ * parse intermediate tasks.
+ *
++ * @deprecated To be replaced
+ */
++ @Deprecated
+ public static abstract class SchedulableTick {
+ private static final AtomicLong ID_GENERATOR = new AtomicLong();
+ public final long id = ID_GENERATOR.getAndIncrement();
@@ -8707,10 +9211,10 @@ index 0000000000000000000000000000000000000000..8197ccb1c4e5878dbd8007b5fb514640
+}
diff --git a/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java
new file mode 100644
-index 0000000000000000000000000000000000000000..212bc9ae2fc7d37d4a089a2921b00de1e97f7cc1
+index 0000000000000000000000000000000000000000..82c4c11b0b564c97ac92bd5f54e3754a7ba95184
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java
-@@ -0,0 +1,272 @@
+@@ -0,0 +1,270 @@
+package ca.spottedleaf.concurrentutil.set;
+
+import java.util.Comparator;
@@ -8721,8 +9225,8 @@ index 0000000000000000000000000000000000000000..212bc9ae2fc7d37d4a089a2921b00de1
+
+ public final Comparator super E> comparator;
+
-+ protected Link head;
-+ protected Link tail;
++ private Link head;
++ private Link tail;
+
+ public LinkedSortedSet() {
+ this((Comparator)Comparator.naturalOrder());
@@ -8970,8 +9474,6 @@ index 0000000000000000000000000000000000000000..212bc9ae2fc7d37d4a089a2921b00de1
+ private Link prev;
+ private Link next;
+
-+ private Link() {}
-+
+ private Link(final E element) {
+ this.element = element;
+ }
@@ -8983,825 +9485,213 @@ index 0000000000000000000000000000000000000000..212bc9ae2fc7d37d4a089a2921b00de1
+ }
+ }
+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/ArrayUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/ArrayUtil.java
+diff --git a/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java
new file mode 100644
-index 0000000000000000000000000000000000000000..ebb1ab06165addb173fea4d295001fe37f4e79d3
+index 0000000000000000000000000000000000000000..bd8eb4f25d1dee00fbf9c05c14b0d94c5c641a55
--- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/util/ArrayUtil.java
-@@ -0,0 +1,816 @@
-+package ca.spottedleaf.concurrentutil.util;
++++ b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java
+@@ -0,0 +1,204 @@
++package ca.spottedleaf.concurrentutil.set;
+
-+import java.lang.invoke.VarHandle;
++import java.util.Iterator;
++import java.util.NoSuchElementException;
++import java.util.Objects;
+
-+public final class ArrayUtil {
++public final class LinkedUnsortedList implements Iterable {
+
-+ public static final VarHandle BOOLEAN_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(boolean[].class);
++ private Link head;
++ private Link tail;
+
-+ public static final VarHandle BYTE_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(byte[].class);
++ public LinkedUnsortedList() {}
+
-+ public static final VarHandle SHORT_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(short[].class);
-+
-+ public static final VarHandle INT_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(int[].class);
-+
-+ public static final VarHandle LONG_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(long[].class);
-+
-+ public static final VarHandle OBJECT_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(Object[].class);
-+
-+ private ArrayUtil() {
-+ throw new RuntimeException();
++ public void clear() {
++ this.head = this.tail = null;
+ }
+
-+ /* byte array */
-+
-+ public static byte getPlain(final byte[] array, final int index) {
-+ return (byte)BYTE_ARRAY_HANDLE.get(array, index);
++ public boolean isEmpty() {
++ return this.head == null;
+ }
+
-+ public static byte getOpaque(final byte[] array, final int index) {
-+ return (byte)BYTE_ARRAY_HANDLE.getOpaque(array, index);
++ public E first() {
++ final Link head = this.head;
++ return head == null ? null : head.element;
+ }
+
-+ public static byte getAcquire(final byte[] array, final int index) {
-+ return (byte)BYTE_ARRAY_HANDLE.getAcquire(array, index);
++ public E last() {
++ final Link tail = this.tail;
++ return tail == null ? null : tail.element;
+ }
+
-+ public static byte getVolatile(final byte[] array, final int index) {
-+ return (byte)BYTE_ARRAY_HANDLE.getVolatile(array, index);
-+ }
-+
-+ public static void setPlain(final byte[] array, final int index, final byte value) {
-+ BYTE_ARRAY_HANDLE.set(array, index, value);
-+ }
-+
-+ public static void setOpaque(final byte[] array, final int index, final byte value) {
-+ BYTE_ARRAY_HANDLE.setOpaque(array, index, value);
-+ }
-+
-+ public static void setRelease(final byte[] array, final int index, final byte value) {
-+ BYTE_ARRAY_HANDLE.setRelease(array, index, value);
-+ }
-+
-+ public static void setVolatile(final byte[] array, final int index, final byte value) {
-+ BYTE_ARRAY_HANDLE.setVolatile(array, index, value);
-+ }
-+
-+ public static void setVolatileContended(final byte[] array, final int index, final byte param) {
-+ int failures = 0;
-+
-+ for (byte curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
++ public boolean containsFirst(final E element) {
++ for (Link curr = this.head; curr != null; curr = curr.next) {
++ if (Objects.equals(element, curr.element)) {
++ return true;
+ }
++ }
++ return false;
++ }
+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) {
-+ return;
++ public boolean containsLast(final E element) {
++ for (Link curr = this.tail; curr != null; curr = curr.prev) {
++ if (Objects.equals(element, curr.element)) {
++ return true;
+ }
+ }
++ return false;
++ }
++
++ private void removeNode(final Link node) {
++ final Link prev = node.prev;
++ final Link next = node.next;
++
++ // help GC
++ node.element = null;
++ node.prev = null;
++ node.next = null;
++
++ if (prev == null) {
++ this.head = next;
++ } else {
++ prev.next = next;
++ }
++
++ if (next == null) {
++ this.tail = prev;
++ } else {
++ next.prev = prev;
++ }
+ }
+
-+ public static byte compareAndExchangeVolatile(final byte[] array, final int index, final byte expect, final byte update) {
-+ return (byte)BYTE_ARRAY_HANDLE.compareAndExchange(array, index, expect, update);
++ public boolean remove(final Link link) {
++ if (link.element == null) {
++ return false;
++ }
++
++ this.removeNode(link);
++ return true;
+ }
+
-+ public static byte getAndAddVolatile(final byte[] array, final int index, final byte param) {
-+ return (byte)BYTE_ARRAY_HANDLE.getAndAdd(array, index, param);
++ public boolean removeFirst(final E element) {
++ for (Link curr = this.head; curr != null; curr = curr.next) {
++ if (Objects.equals(element, curr.element)) {
++ this.removeNode(curr);
++ return true;
++ }
++ }
++ return false;
+ }
+
-+ public static byte getAndAndVolatile(final byte[] array, final int index, final byte param) {
-+ return (byte)BYTE_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param);
++ public boolean removeLast(final E element) {
++ for (Link curr = this.tail; curr != null; curr = curr.prev) {
++ if (Objects.equals(element, curr.element)) {
++ this.removeNode(curr);
++ return true;
++ }
++ }
++ return false;
+ }
+
-+ public static byte getAndOrVolatile(final byte[] array, final int index, final byte param) {
-+ return (byte)BYTE_ARRAY_HANDLE.getAndBitwiseOr(array, index, param);
-+ }
++ @Override
++ public Iterator iterator() {
++ return new Iterator<>() {
++ private Link next = LinkedUnsortedList.this.head;
+
-+ public static byte getAndXorVolatile(final byte[] array, final int index, final byte param) {
-+ return (byte)BYTE_ARRAY_HANDLE.getAndBitwiseXor(array, index, param);
-+ }
-+
-+ public static byte getAndSetVolatile(final byte[] array, final int index, final byte param) {
-+ return (byte)BYTE_ARRAY_HANDLE.getAndSet(array, index, param);
-+ }
-+
-+ public static byte compareAndExchangeVolatileContended(final byte[] array, final int index, final byte expect, final byte update) {
-+ return (byte)BYTE_ARRAY_HANDLE.compareAndExchange(array, index, expect, update);
-+ }
-+
-+ public static byte getAndAddVolatileContended(final byte[] array, final int index, final byte param) {
-+ int failures = 0;
-+
-+ for (byte curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
++ @Override
++ public boolean hasNext() {
++ return this.next != null;
+ }
+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr + param)))) {
-+ return curr;
++ @Override
++ public E next() {
++ final Link next = this.next;
++ if (next == null) {
++ throw new NoSuchElementException();
++ }
++ this.next = next.next;
++ return next.element;
+ }
++ };
++ }
++
++ public E pollFirst() {
++ final Link head = this.head;
++ if (head == null) {
++ return null;
++ }
++
++ final E ret = head.element;
++ final Link next = head.next;
++
++ // unlink head
++ this.head = next;
++ if (next == null) {
++ this.tail = null;
++ } else {
++ next.prev = null;
++ }
++
++ // help GC
++ head.element = null;
++ head.next = null;
++
++ return ret;
++ }
++
++ public E pollLast() {
++ final Link tail = this.tail;
++ if (tail == null) {
++ return null;
++ }
++
++ final E ret = tail.element;
++ final Link prev = tail.prev;
++
++ // unlink tail
++ this.tail = prev;
++ if (prev == null) {
++ this.head = null;
++ } else {
++ prev.next = null;
++ }
++
++ // help GC
++ tail.element = null;
++ tail.prev = null;
++
++ return ret;
++ }
++
++ public Link addLast(final E element) {
++ final Link curr = this.tail;
++ if (curr != null) {
++ return this.tail = new Link<>(element, curr, null);
++ } else {
++ return this.head = this.tail = new Link<>(element);
+ }
+ }
+
-+ public static byte getAndAndVolatileContended(final byte[] array, final int index, final byte param) {
-+ int failures = 0;
-+
-+ for (byte curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr & param)))) {
-+ return curr;
-+ }
++ public Link addFirst(final E element) {
++ final Link curr = this.head;
++ if (curr != null) {
++ return this.head = new Link<>(element, null, curr);
++ } else {
++ return this.head = this.tail = new Link<>(element);
+ }
+ }
+
-+ public static byte getAndOrVolatileContended(final byte[] array, final int index, final byte param) {
-+ int failures = 0;
++ public static final class Link {
++ private E element;
++ private Link prev;
++ private Link next;
+
-+ for (byte curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr | param)))) {
-+ return curr;
-+ }
++ private Link(final E element) {
++ this.element = element;
+ }
-+ }
+
-+ public static byte getAndXorVolatileContended(final byte[] array, final int index, final byte param) {
-+ int failures = 0;
-+
-+ for (byte curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (byte) (curr ^ param)))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ public static byte getAndSetVolatileContended(final byte[] array, final int index, final byte param) {
-+ int failures = 0;
-+
-+ for (byte curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ /* short array */
-+
-+ public static short getPlain(final short[] array, final int index) {
-+ return (short)SHORT_ARRAY_HANDLE.get(array, index);
-+ }
-+
-+ public static short getOpaque(final short[] array, final int index) {
-+ return (short)SHORT_ARRAY_HANDLE.getOpaque(array, index);
-+ }
-+
-+ public static short getAcquire(final short[] array, final int index) {
-+ return (short)SHORT_ARRAY_HANDLE.getAcquire(array, index);
-+ }
-+
-+ public static short getVolatile(final short[] array, final int index) {
-+ return (short)SHORT_ARRAY_HANDLE.getVolatile(array, index);
-+ }
-+
-+ public static void setPlain(final short[] array, final int index, final short value) {
-+ SHORT_ARRAY_HANDLE.set(array, index, value);
-+ }
-+
-+ public static void setOpaque(final short[] array, final int index, final short value) {
-+ SHORT_ARRAY_HANDLE.setOpaque(array, index, value);
-+ }
-+
-+ public static void setRelease(final short[] array, final int index, final short value) {
-+ SHORT_ARRAY_HANDLE.setRelease(array, index, value);
-+ }
-+
-+ public static void setVolatile(final short[] array, final int index, final short value) {
-+ SHORT_ARRAY_HANDLE.setVolatile(array, index, value);
-+ }
-+
-+ public static void setVolatileContended(final short[] array, final int index, final short param) {
-+ int failures = 0;
-+
-+ for (short curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) {
-+ return;
-+ }
-+ }
-+ }
-+
-+ public static short compareAndExchangeVolatile(final short[] array, final int index, final short expect, final short update) {
-+ return (short)SHORT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update);
-+ }
-+
-+ public static short getAndAddVolatile(final short[] array, final int index, final short param) {
-+ return (short)SHORT_ARRAY_HANDLE.getAndAdd(array, index, param);
-+ }
-+
-+ public static short getAndAndVolatile(final short[] array, final int index, final short param) {
-+ return (short)SHORT_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param);
-+ }
-+
-+ public static short getAndOrVolatile(final short[] array, final int index, final short param) {
-+ return (short)SHORT_ARRAY_HANDLE.getAndBitwiseOr(array, index, param);
-+ }
-+
-+ public static short getAndXorVolatile(final short[] array, final int index, final short param) {
-+ return (short)SHORT_ARRAY_HANDLE.getAndBitwiseXor(array, index, param);
-+ }
-+
-+ public static short getAndSetVolatile(final short[] array, final int index, final short param) {
-+ return (short)SHORT_ARRAY_HANDLE.getAndSet(array, index, param);
-+ }
-+
-+ public static short compareAndExchangeVolatileContended(final short[] array, final int index, final short expect, final short update) {
-+ return (short)SHORT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update);
-+ }
-+
-+ public static short getAndAddVolatileContended(final short[] array, final int index, final short param) {
-+ int failures = 0;
-+
-+ for (short curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr + param)))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ public static short getAndAndVolatileContended(final short[] array, final int index, final short param) {
-+ int failures = 0;
-+
-+ for (short curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr & param)))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ public static short getAndOrVolatileContended(final short[] array, final int index, final short param) {
-+ int failures = 0;
-+
-+ for (short curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr | param)))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ public static short getAndXorVolatileContended(final short[] array, final int index, final short param) {
-+ int failures = 0;
-+
-+ for (short curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (short) (curr ^ param)))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ public static short getAndSetVolatileContended(final short[] array, final int index, final short param) {
-+ int failures = 0;
-+
-+ for (short curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ /* int array */
-+
-+ public static int getPlain(final int[] array, final int index) {
-+ return (int)INT_ARRAY_HANDLE.get(array, index);
-+ }
-+
-+ public static int getOpaque(final int[] array, final int index) {
-+ return (int)INT_ARRAY_HANDLE.getOpaque(array, index);
-+ }
-+
-+ public static int getAcquire(final int[] array, final int index) {
-+ return (int)INT_ARRAY_HANDLE.getAcquire(array, index);
-+ }
-+
-+ public static int getVolatile(final int[] array, final int index) {
-+ return (int)INT_ARRAY_HANDLE.getVolatile(array, index);
-+ }
-+
-+ public static void setPlain(final int[] array, final int index, final int value) {
-+ INT_ARRAY_HANDLE.set(array, index, value);
-+ }
-+
-+ public static void setOpaque(final int[] array, final int index, final int value) {
-+ INT_ARRAY_HANDLE.setOpaque(array, index, value);
-+ }
-+
-+ public static void setRelease(final int[] array, final int index, final int value) {
-+ INT_ARRAY_HANDLE.setRelease(array, index, value);
-+ }
-+
-+ public static void setVolatile(final int[] array, final int index, final int value) {
-+ INT_ARRAY_HANDLE.setVolatile(array, index, value);
-+ }
-+
-+ public static void setVolatileContended(final int[] array, final int index, final int param) {
-+ int failures = 0;
-+
-+ for (int curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) {
-+ return;
-+ }
-+ }
-+ }
-+
-+ public static int compareAndExchangeVolatile(final int[] array, final int index, final int expect, final int update) {
-+ return (int)INT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update);
-+ }
-+
-+ public static int getAndAddVolatile(final int[] array, final int index, final int param) {
-+ return (int)INT_ARRAY_HANDLE.getAndAdd(array, index, param);
-+ }
-+
-+ public static int getAndAndVolatile(final int[] array, final int index, final int param) {
-+ return (int)INT_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param);
-+ }
-+
-+ public static int getAndOrVolatile(final int[] array, final int index, final int param) {
-+ return (int)INT_ARRAY_HANDLE.getAndBitwiseOr(array, index, param);
-+ }
-+
-+ public static int getAndXorVolatile(final int[] array, final int index, final int param) {
-+ return (int)INT_ARRAY_HANDLE.getAndBitwiseXor(array, index, param);
-+ }
-+
-+ public static int getAndSetVolatile(final int[] array, final int index, final int param) {
-+ return (int)INT_ARRAY_HANDLE.getAndSet(array, index, param);
-+ }
-+
-+ public static int compareAndExchangeVolatileContended(final int[] array, final int index, final int expect, final int update) {
-+ return (int)INT_ARRAY_HANDLE.compareAndExchange(array, index, expect, update);
-+ }
-+
-+ public static int getAndAddVolatileContended(final int[] array, final int index, final int param) {
-+ int failures = 0;
-+
-+ for (int curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr + param)))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ public static int getAndAndVolatileContended(final int[] array, final int index, final int param) {
-+ int failures = 0;
-+
-+ for (int curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr & param)))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ public static int getAndOrVolatileContended(final int[] array, final int index, final int param) {
-+ int failures = 0;
-+
-+ for (int curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr | param)))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ public static int getAndXorVolatileContended(final int[] array, final int index, final int param) {
-+ int failures = 0;
-+
-+ for (int curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (int) (curr ^ param)))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ public static int getAndSetVolatileContended(final int[] array, final int index, final int param) {
-+ int failures = 0;
-+
-+ for (int curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ /* long array */
-+
-+ public static long getPlain(final long[] array, final int index) {
-+ return (long)LONG_ARRAY_HANDLE.get(array, index);
-+ }
-+
-+ public static long getOpaque(final long[] array, final int index) {
-+ return (long)LONG_ARRAY_HANDLE.getOpaque(array, index);
-+ }
-+
-+ public static long getAcquire(final long[] array, final int index) {
-+ return (long)LONG_ARRAY_HANDLE.getAcquire(array, index);
-+ }
-+
-+ public static long getVolatile(final long[] array, final int index) {
-+ return (long)LONG_ARRAY_HANDLE.getVolatile(array, index);
-+ }
-+
-+ public static void setPlain(final long[] array, final int index, final long value) {
-+ LONG_ARRAY_HANDLE.set(array, index, value);
-+ }
-+
-+ public static void setOpaque(final long[] array, final int index, final long value) {
-+ LONG_ARRAY_HANDLE.setOpaque(array, index, value);
-+ }
-+
-+ public static void setRelease(final long[] array, final int index, final long value) {
-+ LONG_ARRAY_HANDLE.setRelease(array, index, value);
-+ }
-+
-+ public static void setVolatile(final long[] array, final int index, final long value) {
-+ LONG_ARRAY_HANDLE.setVolatile(array, index, value);
-+ }
-+
-+ public static void setVolatileContended(final long[] array, final int index, final long param) {
-+ int failures = 0;
-+
-+ for (long curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) {
-+ return;
-+ }
-+ }
-+ }
-+
-+ public static long compareAndExchangeVolatile(final long[] array, final int index, final long expect, final long update) {
-+ return (long)LONG_ARRAY_HANDLE.compareAndExchange(array, index, expect, update);
-+ }
-+
-+ public static long getAndAddVolatile(final long[] array, final int index, final long param) {
-+ return (long)LONG_ARRAY_HANDLE.getAndAdd(array, index, param);
-+ }
-+
-+ public static long getAndAndVolatile(final long[] array, final int index, final long param) {
-+ return (long)LONG_ARRAY_HANDLE.getAndBitwiseAnd(array, index, param);
-+ }
-+
-+ public static long getAndOrVolatile(final long[] array, final int index, final long param) {
-+ return (long)LONG_ARRAY_HANDLE.getAndBitwiseOr(array, index, param);
-+ }
-+
-+ public static long getAndXorVolatile(final long[] array, final int index, final long param) {
-+ return (long)LONG_ARRAY_HANDLE.getAndBitwiseXor(array, index, param);
-+ }
-+
-+ public static long getAndSetVolatile(final long[] array, final int index, final long param) {
-+ return (long)LONG_ARRAY_HANDLE.getAndSet(array, index, param);
-+ }
-+
-+ public static long compareAndExchangeVolatileContended(final long[] array, final int index, final long expect, final long update) {
-+ return (long)LONG_ARRAY_HANDLE.compareAndExchange(array, index, expect, update);
-+ }
-+
-+ public static long getAndAddVolatileContended(final long[] array, final int index, final long param) {
-+ int failures = 0;
-+
-+ for (long curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr + param)))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ public static long getAndAndVolatileContended(final long[] array, final int index, final long param) {
-+ int failures = 0;
-+
-+ for (long curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr & param)))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ public static long getAndOrVolatileContended(final long[] array, final int index, final long param) {
-+ int failures = 0;
-+
-+ for (long curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr | param)))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ public static long getAndXorVolatileContended(final long[] array, final int index, final long param) {
-+ int failures = 0;
-+
-+ for (long curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (long) (curr ^ param)))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ public static long getAndSetVolatileContended(final long[] array, final int index, final long param) {
-+ int failures = 0;
-+
-+ for (long curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ /* boolean array */
-+
-+ public static boolean getPlain(final boolean[] array, final int index) {
-+ return (boolean)BOOLEAN_ARRAY_HANDLE.get(array, index);
-+ }
-+
-+ public static boolean getOpaque(final boolean[] array, final int index) {
-+ return (boolean)BOOLEAN_ARRAY_HANDLE.getOpaque(array, index);
-+ }
-+
-+ public static boolean getAcquire(final boolean[] array, final int index) {
-+ return (boolean)BOOLEAN_ARRAY_HANDLE.getAcquire(array, index);
-+ }
-+
-+ public static boolean getVolatile(final boolean[] array, final int index) {
-+ return (boolean)BOOLEAN_ARRAY_HANDLE.getVolatile(array, index);
-+ }
-+
-+ public static void setPlain(final boolean[] array, final int index, final boolean value) {
-+ BOOLEAN_ARRAY_HANDLE.set(array, index, value);
-+ }
-+
-+ public static void setOpaque(final boolean[] array, final int index, final boolean value) {
-+ BOOLEAN_ARRAY_HANDLE.setOpaque(array, index, value);
-+ }
-+
-+ public static void setRelease(final boolean[] array, final int index, final boolean value) {
-+ BOOLEAN_ARRAY_HANDLE.setRelease(array, index, value);
-+ }
-+
-+ public static void setVolatile(final boolean[] array, final int index, final boolean value) {
-+ BOOLEAN_ARRAY_HANDLE.setVolatile(array, index, value);
-+ }
-+
-+ public static void setVolatileContended(final boolean[] array, final int index, final boolean param) {
-+ int failures = 0;
-+
-+ for (boolean curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) {
-+ return;
-+ }
-+ }
-+ }
-+
-+ public static boolean compareAndExchangeVolatile(final boolean[] array, final int index, final boolean expect, final boolean update) {
-+ return (boolean)BOOLEAN_ARRAY_HANDLE.compareAndExchange(array, index, expect, update);
-+ }
-+
-+ public static boolean getAndOrVolatile(final boolean[] array, final int index, final boolean param) {
-+ return (boolean)BOOLEAN_ARRAY_HANDLE.getAndBitwiseOr(array, index, param);
-+ }
-+
-+ public static boolean getAndXorVolatile(final boolean[] array, final int index, final boolean param) {
-+ return (boolean)BOOLEAN_ARRAY_HANDLE.getAndBitwiseXor(array, index, param);
-+ }
-+
-+ public static boolean getAndSetVolatile(final boolean[] array, final int index, final boolean param) {
-+ return (boolean)BOOLEAN_ARRAY_HANDLE.getAndSet(array, index, param);
-+ }
-+
-+ public static boolean compareAndExchangeVolatileContended(final boolean[] array, final int index, final boolean expect, final boolean update) {
-+ return (boolean)BOOLEAN_ARRAY_HANDLE.compareAndExchange(array, index, expect, update);
-+ }
-+
-+ public static boolean getAndAndVolatileContended(final boolean[] array, final int index, final boolean param) {
-+ int failures = 0;
-+
-+ for (boolean curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (boolean) (curr & param)))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ public static boolean getAndOrVolatileContended(final boolean[] array, final int index, final boolean param) {
-+ int failures = 0;
-+
-+ for (boolean curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (boolean) (curr | param)))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ public static boolean getAndXorVolatileContended(final boolean[] array, final int index, final boolean param) {
-+ int failures = 0;
-+
-+ for (boolean curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, (boolean) (curr ^ param)))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ public static boolean getAndSetVolatileContended(final boolean[] array, final int index, final boolean param) {
-+ int failures = 0;
-+
-+ for (boolean curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) {
-+ return curr;
-+ }
-+ }
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ public static T getPlain(final T[] array, final int index) {
-+ final Object ret = OBJECT_ARRAY_HANDLE.get((Object[])array, index);
-+ return (T)ret;
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ public static T getOpaque(final T[] array, final int index) {
-+ final Object ret = OBJECT_ARRAY_HANDLE.getOpaque((Object[])array, index);
-+ return (T)ret;
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ public static T getAcquire(final T[] array, final int index) {
-+ final Object ret = OBJECT_ARRAY_HANDLE.getAcquire((Object[])array, index);
-+ return (T)ret;
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ public static T getVolatile(final T[] array, final int index) {
-+ final Object ret = OBJECT_ARRAY_HANDLE.getVolatile((Object[])array, index);
-+ return (T)ret;
-+ }
-+
-+ public static void setPlain(final T[] array, final int index, final T value) {
-+ OBJECT_ARRAY_HANDLE.set((Object[])array, index, (Object)value);
-+ }
-+
-+ public static void setOpaque(final T[] array, final int index, final T value) {
-+ OBJECT_ARRAY_HANDLE.setOpaque((Object[])array, index, (Object)value);
-+ }
-+
-+ public static void setRelease(final T[] array, final int index, final T value) {
-+ OBJECT_ARRAY_HANDLE.setRelease((Object[])array, index, (Object)value);
-+ }
-+
-+ public static void setVolatile(final T[] array, final int index, final T value) {
-+ OBJECT_ARRAY_HANDLE.setVolatile((Object[])array, index, (Object)value);
-+ }
-+
-+ public static void setVolatileContended(final T[] array, final int index, final T param) {
-+ int failures = 0;
-+
-+ for (T curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) {
-+ return;
-+ }
-+ }
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ public static T compareAndExchangeVolatile(final T[] array, final int index, final T expect, final T update) {
-+ final Object ret = OBJECT_ARRAY_HANDLE.compareAndExchange((Object[])array, index, (Object)expect, (Object)update);
-+ return (T)ret;
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ public static T getAndSetVolatile(final T[] array, final int index, final T param) {
-+ final Object ret = BYTE_ARRAY_HANDLE.getAndSet((Object[])array, index, (Object)param);
-+ return (T)ret;
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ public static T compareAndExchangeVolatileContended(final T[] array, final int index, final T expect, final T update) {
-+ final Object ret = OBJECT_ARRAY_HANDLE.compareAndExchange((Object[])array, index, (Object)expect, (Object)update);
-+ return (T)ret;
-+ }
-+
-+ public static T getAndSetVolatileContended(final T[] array, final int index, final T param) {
-+ int failures = 0;
-+
-+ for (T curr = getVolatile(array, index);;++failures) {
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = compareAndExchangeVolatileContended(array, index, curr, param))) {
-+ return curr;
-+ }
++ private Link(final E element, final Link prev, final Link next) {
++ this.element = element;
++ this.prev = prev;
++ this.next = next;
+ }
+ }
+}
@@ -10185,10 +10075,10 @@ index 0000000000000000000000000000000000000000..4e61c477a56e645228d5a2015c268169
+}
diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java
new file mode 100644
-index 0000000000000000000000000000000000000000..77699c5fa9681f9ec7aa05cbb50eb60828e193ab
+index 0000000000000000000000000000000000000000..9d7b9b8158cd01d12adbd7896ff77bee9828e101
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java
-@@ -0,0 +1,176 @@
+@@ -0,0 +1,196 @@
+package ca.spottedleaf.concurrentutil.util;
+
+public final class IntegerUtil {
@@ -10361,11 +10251,183 @@ index 0000000000000000000000000000000000000000..77699c5fa9681f9ec7aa05cbb50eb608
+ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1
+ }
+
++ // https://lemire.me/blog/2019/02/08/faster-remainders-when-the-divisor-is-a-constant-beating-compilers-and-libdivide
++ /**
++ *
++ * Usage:
++ *
++ * {@code
++ * static final long mult = getSimpleMultiplier(divisor, bits);
++ * long x = ...;
++ * long magic = x * mult;
++ * long divQ = magic >>> bits;
++ * long divR = ((magic & ((1 << bits) - 1)) * divisor) >>> bits;
++ * }
++ *
++ *
++ * @param bits The number of bits of precision for the returned result
++ */
++ public static long getUnsignedDivisorMagic(final long divisor, final int bits) {
++ return (((1L << bits) - 1L) / divisor) + 1;
++ }
++
+ private IntegerUtil() {
+ throw new RuntimeException();
+ }
+}
\ No newline at end of file
+diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java b/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..2919bbaa07b70f182438c3be8f9ebbe0649809b6
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java
+@@ -0,0 +1,145 @@
++package ca.spottedleaf.concurrentutil.util;
++
++public enum Priority {
++
++ /**
++ * Priority value indicating the task has completed or is being completed.
++ * This priority cannot be used to schedule tasks.
++ */
++ COMPLETING(-1),
++
++ /**
++ * Absolute highest priority, should only be used for when a task is blocking a time-critical thread.
++ */
++ BLOCKING(),
++
++ /**
++ * Should only be used for urgent but not time-critical tasks.
++ */
++ HIGHEST(),
++
++ /**
++ * Two priorities above normal.
++ */
++ HIGHER(),
++
++ /**
++ * One priority above normal.
++ */
++ HIGH(),
++
++ /**
++ * Default priority.
++ */
++ NORMAL(),
++
++ /**
++ * One priority below normal.
++ */
++ LOW(),
++
++ /**
++ * Two priorities below normal.
++ */
++ LOWER(),
++
++ /**
++ * Use for tasks that should eventually execute, but are not needed to.
++ */
++ LOWEST(),
++
++ /**
++ * Use for tasks that can be delayed indefinitely.
++ */
++ IDLE();
++
++ // returns whether the priority can be scheduled
++ public static boolean isValidPriority(final Priority priority) {
++ return priority != null && priority != priority.COMPLETING;
++ }
++
++ // returns the higher priority of the two
++ public static Priority max(final Priority p1, final Priority p2) {
++ return p1.isHigherOrEqualPriority(p2) ? p1 : p2;
++ }
++
++ // returns the lower priroity of the two
++ public static Priority min(final Priority p1, final Priority p2) {
++ return p1.isLowerOrEqualPriority(p2) ? p1 : p2;
++ }
++
++ public boolean isHigherOrEqualPriority(final Priority than) {
++ return this.priority <= than.priority;
++ }
++
++ public boolean isHigherPriority(final Priority than) {
++ return this.priority < than.priority;
++ }
++
++ public boolean isLowerOrEqualPriority(final Priority than) {
++ return this.priority >= than.priority;
++ }
++
++ public boolean isLowerPriority(final Priority than) {
++ return this.priority > than.priority;
++ }
++
++ public boolean isHigherOrEqualPriority(final int than) {
++ return this.priority <= than;
++ }
++
++ public boolean isHigherPriority(final int than) {
++ return this.priority < than;
++ }
++
++ public boolean isLowerOrEqualPriority(final int than) {
++ return this.priority >= than;
++ }
++
++ public boolean isLowerPriority(final int than) {
++ return this.priority > than;
++ }
++
++ public static boolean isHigherOrEqualPriority(final int priority, final int than) {
++ return priority <= than;
++ }
++
++ public static boolean isHigherPriority(final int priority, final int than) {
++ return priority < than;
++ }
++
++ public static boolean isLowerOrEqualPriority(final int priority, final int than) {
++ return priority >= than;
++ }
++
++ public static boolean isLowerPriority(final int priority, final int than) {
++ return priority > than;
++ }
++
++ static final Priority[] PRIORITIES = Priority.values();
++
++ /** includes special priorities */
++ public static final int TOTAL_PRIORITIES = PRIORITIES.length;
++
++ public static final int TOTAL_SCHEDULABLE_PRIORITIES = TOTAL_PRIORITIES - 1;
++
++ public static Priority getPriority(final int priority) {
++ return PRIORITIES[priority + 1];
++ }
++
++ private static int priorityCounter;
++
++ private static int nextCounter() {
++ return priorityCounter++;
++ }
++
++ public final int priority;
++
++ private Priority() {
++ this(nextCounter());
++ }
++
++ private Priority(final int priority) {
++ this.priority = priority;
++ }
++}
+\ No newline at end of file
diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/ThrowUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/ThrowUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..a3a8b5c6795c4d116e094e4c910553416f565b93
diff --git a/patches/server/0009-MC-Utils.patch b/patches/server/0009-MC-Utils.patch
index 310d5c2f29..b7549f7365 100644
--- a/patches/server/0009-MC-Utils.patch
+++ b/patches/server/0009-MC-Utils.patch
@@ -12,12 +12,135 @@ public net.minecraft.server.level.ServerChunkCache mainThreadProcessor
public net.minecraft.server.level.ServerChunkCache$MainThreadExecutor
public net.minecraft.world.level.chunk.LevelChunkSection states
+diff --git a/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java b/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..6c98d420ea84c10ef4f15d4deb3f04e610ed8548
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/common/PlatformHooks.java
+@@ -0,0 +1,117 @@
++package ca.spottedleaf.moonrise.common;
++
++import com.mojang.datafixers.DSL;
++import com.mojang.datafixers.DataFixer;
++import net.minecraft.core.BlockPos;
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.server.level.ChunkHolder;
++import net.minecraft.server.level.GenerationChunkHolder;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.level.ServerPlayer;
++import net.minecraft.world.entity.Entity;
++import net.minecraft.world.level.BlockGetter;
++import net.minecraft.world.level.ChunkPos;
++import net.minecraft.world.level.Level;
++import net.minecraft.world.level.block.state.BlockState;
++import net.minecraft.world.level.chunk.ChunkAccess;
++import net.minecraft.world.level.chunk.LevelChunk;
++import net.minecraft.world.level.chunk.ProtoChunk;
++import net.minecraft.world.level.chunk.storage.SerializableChunkData;
++import net.minecraft.world.level.entity.EntityTypeTest;
++import net.minecraft.world.phys.AABB;
++import java.util.List;
++import java.util.ServiceLoader;
++import java.util.function.Predicate;
++
++public interface PlatformHooks {
++ public static PlatformHooks get() {
++ return Holder.INSTANCE;
++ }
++
++ public String getBrand();
++
++ public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos);
++
++ public Predicate maybeHasLightEmission();
++
++ public boolean hasCurrentlyLoadingChunk();
++
++ public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder);
++
++ public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk);
++
++ public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original);
++
++ public boolean allowAsyncTicketUpdates();
++
++ public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel);
++
++ public void chunkUnloadFromWorld(final LevelChunk chunk);
++
++ public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data);
++
++ public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player);
++
++ public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player);
++
++ public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate super Entity> predicate,
++ final List into);
++
++ public void addToGetEntities(final Level world, final EntityTypeTest entityTypeTest,
++ final AABB boundingBox, final Predicate super T> predicate,
++ final List super T> into, final int maxCount);
++
++ public void entityMove(final Entity entity, final long oldSection, final long newSection);
++
++ public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event);
++
++ public boolean configFixMC224294();
++
++ public boolean configAutoConfigSendDistance();
++
++ public double configPlayerMaxLoadRate();
++
++ public double configPlayerMaxGenRate();
++
++ public double configPlayerMaxSendRate();
++
++ public int configPlayerMaxConcurrentLoads();
++
++ public int configPlayerMaxConcurrentGens();
++
++ public long configAutoSaveInterval(final ServerLevel world);
++
++ public int configMaxAutoSavePerTick(final ServerLevel world);
++
++ public boolean configFixMC159283();
++
++ // support for CB chunk mustNotSave
++ public boolean forceNoSave(final ChunkAccess chunk);
++
++ public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
++ final int fromVersion, final int toVersion);
++
++ public boolean hasMainChunkLoadHook();
++
++ public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData);
++
++ public List modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List entities);
++
++ public void unloadEntity(final Entity entity);
++
++ public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk);
++
++ public int modifyEntityTrackingRange(final Entity entity, final int currentRange);
++
++ public static final class Holder {
++ private Holder() {
++ }
++
++ private static final PlatformHooks INSTANCE;
++
++ static {
++ INSTANCE = ServiceLoader.load(PlatformHooks.class, PlatformHooks.class.getClassLoader()).findFirst()
++ .orElseThrow(() -> new RuntimeException("Failed to locate PlatformHooks"));
++ }
++ }
++}
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java
new file mode 100644
-index 0000000000000000000000000000000000000000..ba68998f6ef57b24c72fd833bd7de440de9501cc
+index 0000000000000000000000000000000000000000..7fed43a1e7bcf35c4d7fd3224837a47fedd59860
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/EntityList.java
-@@ -0,0 +1,129 @@
+@@ -0,0 +1,128 @@
+package ca.spottedleaf.moonrise.common.list;
+
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
@@ -33,15 +156,15 @@ index 0000000000000000000000000000000000000000..ba68998f6ef57b24c72fd833bd7de440
+ */
+public final class EntityList implements Iterable {
+
-+ protected final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f);
++ private final Int2IntOpenHashMap entityToIndex = new Int2IntOpenHashMap(2, 0.8f);
+ {
+ this.entityToIndex.defaultReturnValue(Integer.MIN_VALUE);
+ }
+
-+ protected static final Entity[] EMPTY_LIST = new Entity[0];
++ private static final Entity[] EMPTY_LIST = new Entity[0];
+
-+ protected Entity[] entities = EMPTY_LIST;
-+ protected int count;
++ private Entity[] entities = EMPTY_LIST;
++ private int count;
+
+ public int size() {
+ return this.count;
@@ -114,10 +237,9 @@ index 0000000000000000000000000000000000000000..ba68998f6ef57b24c72fd833bd7de440
+
+ @Override
+ public Iterator iterator() {
-+ return new Iterator() {
-+
-+ Entity lastRet;
-+ int current;
++ return new Iterator<>() {
++ private Entity lastRet;
++ private int current;
+
+ @Override
+ public boolean hasNext() {
@@ -147,138 +269,89 @@ index 0000000000000000000000000000000000000000..ba68998f6ef57b24c72fd833bd7de440
+ };
+ }
+}
-diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java
+diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java
new file mode 100644
-index 0000000000000000000000000000000000000000..fcfbca333234c09f7c056bbfcd9ac8860b20a8db
+index 0000000000000000000000000000000000000000..9f3b25bb2439f283f878db93973a02fcdcd14eed
--- /dev/null
-+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IBlockDataList.java
-@@ -0,0 +1,125 @@
++++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IntList.java
+@@ -0,0 +1,77 @@
+package ca.spottedleaf.moonrise.common.list;
+
-+import it.unimi.dsi.fastutil.longs.LongIterator;
-+import it.unimi.dsi.fastutil.shorts.Short2LongOpenHashMap;
++import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
+import java.util.Arrays;
-+import net.minecraft.world.level.block.Block;
-+import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.world.level.chunk.GlobalPalette;
+
-+public final class IBlockDataList {
++public final class IntList {
+
-+ private static final GlobalPalette GLOBAL_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY);
-+
-+ // map of location -> (index | (location << 16) | (palette id << 32))
-+ private final Short2LongOpenHashMap map = new Short2LongOpenHashMap(2, 0.8f);
++ private final Int2IntOpenHashMap map = new Int2IntOpenHashMap();
+ {
-+ this.map.defaultReturnValue(Long.MAX_VALUE);
++ this.map.defaultReturnValue(Integer.MIN_VALUE);
+ }
+
-+ private static final long[] EMPTY_LIST = new long[0];
++ private static final int[] EMPTY_LIST = new int[0];
+
-+ private long[] byIndex = EMPTY_LIST;
-+ private int size;
-+
-+ public static int getLocationKey(final int x, final int y, final int z) {
-+ return (x & 15) | (((z & 15) << 4)) | ((y & 255) << (4 + 4));
-+ }
-+
-+ public static BlockState getBlockDataFromRaw(final long raw) {
-+ return GLOBAL_PALETTE.valueFor((int)(raw >>> 32));
-+ }
-+
-+ public static int getIndexFromRaw(final long raw) {
-+ return (int)(raw & 0xFFFF);
-+ }
-+
-+ public static int getLocationFromRaw(final long raw) {
-+ return (int)((raw >>> 16) & 0xFFFF);
-+ }
-+
-+ public static long getRawFromValues(final int index, final int location, final BlockState data) {
-+ return (long)index | ((long)location << 16) | (((long)GLOBAL_PALETTE.idFor(data)) << 32);
-+ }
-+
-+ public static long setIndexRawValues(final long value, final int index) {
-+ return value & ~(0xFFFF) | (index);
-+ }
-+
-+ public long add(final int x, final int y, final int z, final BlockState data) {
-+ return this.add(getLocationKey(x, y, z), data);
-+ }
-+
-+ public long add(final int location, final BlockState data) {
-+ final long curr = this.map.get((short)location);
-+
-+ if (curr == Long.MAX_VALUE) {
-+ final int index = this.size++;
-+ final long raw = getRawFromValues(index, location, data);
-+ this.map.put((short)location, raw);
-+
-+ if (index >= this.byIndex.length) {
-+ this.byIndex = Arrays.copyOf(this.byIndex, (int)Math.max(4L, this.byIndex.length * 2L));
-+ }
-+
-+ this.byIndex[index] = raw;
-+ return raw;
-+ } else {
-+ final int index = getIndexFromRaw(curr);
-+ final long raw = this.byIndex[index] = getRawFromValues(index, location, data);
-+
-+ this.map.put((short)location, raw);
-+
-+ return raw;
-+ }
-+ }
-+
-+ public long remove(final int x, final int y, final int z) {
-+ return this.remove(getLocationKey(x, y, z));
-+ }
-+
-+ public long remove(final int location) {
-+ final long ret = this.map.remove((short)location);
-+ final int index = getIndexFromRaw(ret);
-+ if (ret == Long.MAX_VALUE) {
-+ return ret;
-+ }
-+
-+ // move the entry at the end to this index
-+ final int endIndex = --this.size;
-+ final long end = this.byIndex[endIndex];
-+ if (index != endIndex) {
-+ // not empty after this call
-+ this.map.put((short)getLocationFromRaw(end), setIndexRawValues(end, index));
-+ }
-+ this.byIndex[index] = end;
-+ this.byIndex[endIndex] = 0L;
-+
-+ return ret;
-+ }
++ private int[] byIndex = EMPTY_LIST;
++ private int count;
+
+ public int size() {
-+ return this.size;
++ return this.count;
+ }
+
-+ public long getRaw(final int index) {
++ public void setMinCapacity(final int len) {
++ final int[] byIndex = this.byIndex;
++ if (byIndex.length < len) {
++ this.byIndex = Arrays.copyOf(byIndex, len);
++ }
++ }
++
++ public int getRaw(final int index) {
+ return this.byIndex[index];
+ }
+
-+ public int getLocation(final int index) {
-+ return getLocationFromRaw(this.getRaw(index));
++ public boolean add(final int value) {
++ final int count = this.count;
++ final int currIndex = this.map.putIfAbsent(value, count);
++
++ if (currIndex != Integer.MIN_VALUE) {
++ return false; // already in this list
++ }
++
++ int[] list = this.byIndex;
++
++ if (list.length == count) {
++ // resize required
++ list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative
++ }
++
++ list[count] = value;
++ this.count = count + 1;
++
++ return true;
+ }
+
-+ public BlockState getData(final int index) {
-+ return getBlockDataFromRaw(this.getRaw(index));
++ public boolean remove(final int value) {
++ final int index = this.map.remove(value);
++ if (index == Integer.MIN_VALUE) {
++ return false;
++ }
++
++ // move the entry at the end to this index
++ final int endIndex = --this.count;
++ final int end = this.byIndex[endIndex];
++ if (index != endIndex) {
++ // not empty after this call
++ this.map.put(end, index);
++ }
++ this.byIndex[index] = end;
++ this.byIndex[endIndex] = 0;
++
++ return true;
+ }
+
+ public void clear() {
-+ this.size = 0;
++ this.count = 0;
+ this.map.clear();
+ }
-+
-+ public LongIterator getRawIterator() {
-+ return this.map.values().iterator();
-+ }
+}
-\ No newline at end of file
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java
new file mode 100644
index 0000000000000000000000000000000000000000..c21e00812f1aaa1279834a0562d360d6b89e146c
@@ -745,6 +818,89 @@ index 0000000000000000000000000000000000000000..2e876b918672e8ef3b5197b7e6b15972
+ };
+ }
+}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..2bae9949ef325d0001aa638150fbbdf968367e75
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/common/list/ShortList.java
+@@ -0,0 +1,77 @@
++package ca.spottedleaf.moonrise.common.list;
++
++import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap;
++import java.util.Arrays;
++
++public final class ShortList {
++
++ private final Short2ShortOpenHashMap map = new Short2ShortOpenHashMap();
++ {
++ this.map.defaultReturnValue(Short.MIN_VALUE);
++ }
++
++ private static final short[] EMPTY_LIST = new short[0];
++
++ private short[] byIndex = EMPTY_LIST;
++ private short count;
++
++ public int size() {
++ return (int)this.count;
++ }
++
++ public short getRaw(final int index) {
++ return this.byIndex[index];
++ }
++
++ public void setMinCapacity(final int len) {
++ final short[] byIndex = this.byIndex;
++ if (byIndex.length < len) {
++ this.byIndex = Arrays.copyOf(byIndex, len);
++ }
++ }
++
++ public boolean add(final short value) {
++ final int count = (int)this.count;
++ final short currIndex = this.map.putIfAbsent(value, (short)count);
++
++ if (currIndex != Short.MIN_VALUE) {
++ return false; // already in this list
++ }
++
++ short[] list = this.byIndex;
++
++ if (list.length == count) {
++ // resize required
++ list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative
++ }
++
++ list[count] = value;
++ this.count = (short)(count + 1);
++
++ return true;
++ }
++
++ public boolean remove(final short value) {
++ final short index = this.map.remove(value);
++ if (index == Short.MIN_VALUE) {
++ return false;
++ }
++
++ // move the entry at the end to this index
++ final short endIndex = --this.count;
++ final short end = this.byIndex[endIndex];
++ if (index != endIndex) {
++ // not empty after this call
++ this.map.put(end, index);
++ }
++ this.byIndex[(int)index] = end;
++ this.byIndex[(int)endIndex] = (short)0;
++
++ return true;
++ }
++
++ public void clear() {
++ this.count = (short)0;
++ this.map.clear();
++ }
++}
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/list/SortedList.java b/src/main/java/ca/spottedleaf/moonrise/common/list/SortedList.java
new file mode 100644
index 0000000000000000000000000000000000000000..db92261a6cb3758391108361096417c61bc82cdc
@@ -2413,25 +2569,57 @@ index 0000000000000000000000000000000000000000..ab2fa1563d5e32a5313dfcc1da411cab
+ }
+ }
+}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..c2d917c2eac55b8a4411a6e159f177f9428b1150
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/LazyRunnable.java
+@@ -0,0 +1,22 @@
++package ca.spottedleaf.moonrise.common.misc;
++
++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
++import java.lang.invoke.VarHandle;
++
++public final class LazyRunnable implements Runnable {
++
++ private volatile Runnable toRun;
++ private static final VarHandle TO_RUN_HANDLE = ConcurrentUtil.getVarHandle(LazyRunnable.class, "toRun", Runnable.class);
++
++ public void setRunnable(final Runnable run) {
++ final Runnable prev = (Runnable)TO_RUN_HANDLE.compareAndExchange(this, (Runnable)null, run);
++ if (prev != null) {
++ throw new IllegalStateException("Runnable already set");
++ }
++ }
++
++ @Override
++ public void run() {
++ ((Runnable)TO_RUN_HANDLE.getVolatile(this)).run();
++ }
++}
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
new file mode 100644
-index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f5217345c4eba05
+index 0000000000000000000000000000000000000000..bb44de17a37082e57f2292a4f470740be1d09b11
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/NearbyPlayers.java
-@@ -0,0 +1,211 @@
+@@ -0,0 +1,273 @@
+package ca.spottedleaf.moonrise.common.misc;
+
+import ca.spottedleaf.moonrise.common.list.ReferenceList;
+import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
+import ca.spottedleaf.moonrise.common.util.MoonriseConstants;
+import ca.spottedleaf.moonrise.common.util.ChunkSystem;
++import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel;
++import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
+import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants;
++import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel;
+import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
+import net.minecraft.core.BlockPos;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.level.ChunkPos;
++import java.util.ArrayList;
+
+public final class NearbyPlayers {
+
@@ -2441,7 +2629,27 @@ index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f521734
+ GENERAL_REALLY_SMALL,
+ TICK_VIEW_DISTANCE,
+ VIEW_DISTANCE,
-+ SPAWN_RANGE, // Moonrise - chunk tick iteration
++ // Moonrise start - chunk tick iteration
++ SPAWN_RANGE {
++ @Override
++ void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) {
++ ((ChunkTickServerLevel)world).moonrise$addPlayerTickingRequest(chunkX, chunkZ);
++ }
++
++ @Override
++ void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) {
++ ((ChunkTickServerLevel)world).moonrise$removePlayerTickingRequest(chunkX, chunkZ);
++ }
++ };
++ // Moonrise end - chunk tick iteration
++
++ void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) {
++
++ }
++
++ void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) {
++
++ }
+ }
+
+ private static final NearbyMapType[] MAP_TYPES = NearbyMapType.values();
@@ -2458,6 +2666,12 @@ index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f521734
+ private final ServerLevel world;
+ private final Reference2ReferenceOpenHashMap players = new Reference2ReferenceOpenHashMap<>();
+ private final Long2ReferenceOpenHashMap byChunk = new Long2ReferenceOpenHashMap<>();
++ private final Long2ReferenceOpenHashMap>[] directByChunk = new Long2ReferenceOpenHashMap[TOTAL_MAP_TYPES];
++ {
++ for (int i = 0; i < this.directByChunk.length; ++i) {
++ this.directByChunk[i] = new Long2ReferenceOpenHashMap<>();
++ }
++ }
+
+ public NearbyPlayers(final ServerLevel world) {
+ this.world = world;
@@ -2491,6 +2705,16 @@ index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f521734
+ }
+ }
+
++ public void clear() {
++ if (this.players.isEmpty()) {
++ return;
++ }
++
++ for (final ServerPlayer player : new ArrayList<>(this.players.keySet())) {
++ this.removePlayer(player);
++ }
++ }
++
+ public void tickPlayer(final ServerPlayer player) {
+ final TrackedPlayer[] players = this.players.get(player);
+ if (players == null) {
@@ -2515,38 +2739,41 @@ index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f521734
+ return this.byChunk.get(CoordinateUtils.getChunkKey(pos));
+ }
+
-+ public ReferenceList getPlayers(final BlockPos pos, final NearbyMapType type) {
-+ final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos));
++ public TrackedChunk getChunk(final int chunkX, final int chunkZ) {
++ return this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
++ }
+
-+ return chunk == null ? null : chunk.players[type.ordinal()];
++ public ReferenceList getPlayers(final BlockPos pos, final NearbyMapType type) {
++ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos));
+ }
+
+ public ReferenceList getPlayers(final ChunkPos pos, final NearbyMapType type) {
-+ final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos));
-+
-+ return chunk == null ? null : chunk.players[type.ordinal()];
++ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos));
+ }
+
+ public ReferenceList getPlayersByChunk(final int chunkX, final int chunkZ, final NearbyMapType type) {
-+ final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
-+
-+ return chunk == null ? null : chunk.players[type.ordinal()];
++ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
+ }
+
+ public ReferenceList getPlayersByBlock(final int blockX, final int blockZ, final NearbyMapType type) {
-+ final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4));
-+
-+ return chunk == null ? null : chunk.players[type.ordinal()];
++ return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4));
+ }
+
+ public static final class TrackedChunk {
+
+ private static final ServerPlayer[] EMPTY_PLAYERS_ARRAY = new ServerPlayer[0];
+
++ private final long chunkKey;
++ private final NearbyPlayers nearbyPlayers;
+ private final ReferenceList[] players = new ReferenceList[TOTAL_MAP_TYPES];
+ private int nonEmptyLists;
+ private long updateCount;
+
++ public TrackedChunk(final long chunkKey, final NearbyPlayers nearbyPlayers) {
++ this.chunkKey = chunkKey;
++ this.nearbyPlayers = nearbyPlayers;
++ }
++
+ public boolean isEmpty() {
+ return this.nonEmptyLists == 0;
+ }
@@ -2566,7 +2793,9 @@ index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f521734
+ final ReferenceList list = this.players[idx];
+ if (list == null) {
+ ++this.nonEmptyLists;
-+ (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)).add(player);
++ final ReferenceList players = (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY));
++ this.nearbyPlayers.directByChunk[idx].put(this.chunkKey, players);
++ players.add(player);
+ return;
+ }
+
@@ -2590,6 +2819,7 @@ index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f521734
+
+ if (list.size() == 0) {
+ this.players[idx] = null;
++ this.nearbyPlayers.directByChunk[idx].remove(this.chunkKey);
+ --this.nonEmptyLists;
+ }
+ }
@@ -2608,9 +2838,19 @@ index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f521734
+ protected void addCallback(final ServerPlayer parameter, final int chunkX, final int chunkZ) {
+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
+
-+ NearbyPlayers.this.byChunk.computeIfAbsent(chunkKey, (final long keyInMap) -> {
-+ return new TrackedChunk();
-+ }).addPlayer(parameter, this.type);
++ final TrackedChunk chunk = NearbyPlayers.this.byChunk.get(chunkKey);
++ final NearbyMapType type = this.type;
++ if (chunk != null) {
++ chunk.addPlayer(parameter, type);
++ type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ);
++ } else {
++ final TrackedChunk created = new TrackedChunk(chunkKey, NearbyPlayers.this);
++ NearbyPlayers.this.byChunk.put(chunkKey, created);
++ created.addPlayer(parameter, type);
++ type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ);
++
++ ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$requestChunkData(chunkKey).nearbyPlayers = created;
++ }
+ }
+
+ @Override
@@ -2622,24 +2862,31 @@ index 0000000000000000000000000000000000000000..ab093b0e8ac6f762921eb1d15f521734
+ throw new IllegalStateException("Chunk should exist at " + new ChunkPos(chunkKey));
+ }
+
-+ chunk.removePlayer(parameter, this.type);
++ final NearbyMapType type = this.type;
++ chunk.removePlayer(parameter, type);
++ type.removeFrom(parameter, NearbyPlayers.this.world, chunkX, chunkZ);
+
+ if (chunk.isEmpty()) {
+ NearbyPlayers.this.byChunk.remove(chunkKey);
++ final ChunkData chunkData = ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$releaseChunkData(chunkKey);
++ if (chunkData != null) {
++ chunkData.nearbyPlayers = null;
++ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java b/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java
new file mode 100644
-index 0000000000000000000000000000000000000000..efefd94b652228d877db5dbca8b28354ad42529f
+index 0000000000000000000000000000000000000000..90560769d09538f7a740753a41a3b8e017b0b92a
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/moonrise/common/misc/PositionCountingAreaMap.java
-@@ -0,0 +1,94 @@
+@@ -0,0 +1,99 @@
+package ca.spottedleaf.moonrise.common.misc;
+
+import ca.spottedleaf.concurrentutil.util.IntPairUtil;
+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
++import it.unimi.dsi.fastutil.longs.LongSet;
+import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ReferenceSet;
+
@@ -2652,6 +2899,10 @@ index 0000000000000000000000000000000000000000..efefd94b652228d877db5dbca8b28354
+ return this.counters.keySet();
+ }
+
++ public LongSet getPositions() {
++ return this.positions.keySet();
++ }
++
+ public int getTotalPositions() {
+ return this.positions.size();
+ }
@@ -3060,13 +3311,14 @@ index 0000000000000000000000000000000000000000..4123edddc556c47f3f8d83523c125fd2
+}
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
new file mode 100644
-index 0000000000000000000000000000000000000000..da323a1105347d5cf4b946df10ded78a953236f2
+index 0000000000000000000000000000000000000000..94bba2b71918d79f54b3e28c35e76098ba0afd8c
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/ChunkSystem.java
-@@ -0,0 +1,284 @@
+@@ -0,0 +1,288 @@
+package ca.spottedleaf.moonrise.common.util;
+
-+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
++import ca.spottedleaf.concurrentutil.util.Priority;
++import ca.spottedleaf.moonrise.common.PlatformHooks;
+import com.mojang.logging.LogUtils;
+import net.minecraft.server.level.ChunkHolder;
+import net.minecraft.server.level.FullChunkStatus;
@@ -3090,15 +3342,15 @@ index 0000000000000000000000000000000000000000..da323a1105347d5cf4b946df10ded78a
+ }
+
+ public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) {
-+ scheduleChunkTask(level, chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL);
++ scheduleChunkTask(level, chunkX, chunkZ, run, Priority.NORMAL);
+ }
+
-+ public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final PrioritisedExecutor.Priority priority) {
++ public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final Priority priority) {
+ level.chunkSource.mainThreadProcessor.execute(run);
+ }
+
+ public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
-+ final ChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority,
++ final ChunkStatus toStatus, final boolean addTicket, final Priority priority,
+ final Consumer onComplete) {
+ if (gen) {
+ scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
@@ -3125,7 +3377,7 @@ index 0000000000000000000000000000000000000000..da323a1105347d5cf4b946df10ded78a
+
+ private static long chunkLoadCounter = 0L;
+ public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
-+ final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer onComplete) {
++ final boolean addTicket, final Priority priority, final Consumer onComplete) {
+ if (!org.bukkit.Bukkit.isPrimaryThread()) {
+ scheduleChunkTask(level, chunkX, chunkZ, () -> {
+ scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
@@ -3179,13 +3431,13 @@ index 0000000000000000000000000000000000000000..da323a1105347d5cf4b946df10ded78a
+ }
+ loadCallback.accept(result.orElse(null));
+ }, (final Runnable r) -> {
-+ scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST);
++ scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST);
+ });
+ }
+
+ public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
+ final FullChunkStatus toStatus, final boolean addTicket,
-+ final PrioritisedExecutor.Priority priority, final Consumer onComplete) {
++ final Priority priority, final Consumer onComplete) {
+ // This method goes unused until the chunk system rewrite
+ if (toStatus == FullChunkStatus.INACCESSIBLE) {
+ throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status");
@@ -3262,7 +3514,7 @@ index 0000000000000000000000000000000000000000..da323a1105347d5cf4b946df10ded78a
+ }
+ loadCallback.accept(result.orElse(null));
+ }, (final Runnable r) -> {
-+ scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST);
++ scheduleChunkTask(level, chunkX, chunkZ, r, Priority.HIGHEST);
+ });
+ }
+
@@ -3286,7 +3538,10 @@ index 0000000000000000000000000000000000000000..da323a1105347d5cf4b946df10ded78a
+ return getUpdatingChunkHolderCount(level) != 0;
+ }
+
-+ public static boolean screenEntity(final ServerLevel level, final Entity entity) {
++ public static boolean screenEntity(final ServerLevel level, final Entity entity, final boolean fromDisk, final boolean event) {
++ if (!PlatformHooks.get().screenEntity(level, entity, fromDisk, event)) {
++ return false;
++ }
+ return true;
+ }
+
@@ -3640,93 +3895,212 @@ index 0000000000000000000000000000000000000000..91efda726b87a8a8f28dee84e31b6a70
+}
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java
new file mode 100644
-index 0000000000000000000000000000000000000000..ac6f284ee4469d16c5655328b2488d7612832353
+index 0000000000000000000000000000000000000000..97848869df61648fc415e4d39f409f433202c274
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MixinWorkarounds.java
-@@ -0,0 +1,10 @@
+@@ -0,0 +1,14 @@
+package ca.spottedleaf.moonrise.common.util;
+
+public final class MixinWorkarounds {
+
+ // mixins tries to find the owner of the clone() method, which doesn't exist and NPEs
++ // https://github.com/FabricMC/Mixin/pull/147
+ public static long[] clone(final long[] values) {
+ return values.clone();
+ }
+
++ public static byte[] clone(final byte[] values) {
++ return values.clone();
++ }
+}
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java
new file mode 100644
-index 0000000000000000000000000000000000000000..3abe0bd2a820352b85306d554bf14a4cf6123091
+index 0000000000000000000000000000000000000000..c125c70a68130be373acc989053a6c0e487be924
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseCommon.java
-@@ -0,0 +1,46 @@
+@@ -0,0 +1,101 @@
+package ca.spottedleaf.moonrise.common.util;
+
-+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool;
++import ca.spottedleaf.concurrentutil.executor.thread.PrioritisedThreadPool;
++import ca.spottedleaf.moonrise.common.PlatformHooks;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-+import java.io.File;
++import java.util.concurrent.TimeUnit;
++import java.util.concurrent.atomic.AtomicInteger;
++import java.util.function.Consumer;
+
+public final class MoonriseCommon {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(MoonriseCommon.class);
+
-+ // Paper start
-+ public static PrioritisedThreadPool WORKER_POOL;
-+ public static int WORKER_THREADS;
-+ public static void init(io.papermc.paper.configuration.GlobalConfiguration.ChunkSystem chunkSystem) {
-+ // Paper end
-+ int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2;
-+ if (defaultWorkerThreads <= 4) {
-+ defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2;
-+ } else {
-+ defaultWorkerThreads = defaultWorkerThreads / 2;
-+ }
-+ defaultWorkerThreads = Integer.getInteger("Paper.WorkerThreadCount", Integer.valueOf(defaultWorkerThreads)); // Paper
++ public static final PrioritisedThreadPool WORKER_POOL = new PrioritisedThreadPool(
++ new Consumer<>() {
++ private final AtomicInteger idGenerator = new AtomicInteger();
+
-+ int workerThreads = chunkSystem.workerThreads; // Paper
-+
-+ if (workerThreads <= 0) {
-+ workerThreads = defaultWorkerThreads;
-+ }
-+
-+ WORKER_POOL = new PrioritisedThreadPool(
-+ "Paper Worker Pool", workerThreads, // Paper
-+ (final Thread thread, final Integer id) -> {
-+ thread.setName("Paper Common Worker #" + id.intValue()); // Paper
++ @Override
++ public void accept(Thread thread) {
++ thread.setDaemon(true);
++ thread.setName(PlatformHooks.get().getBrand() + " Common Worker #" + this.idGenerator.getAndIncrement());
+ thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(final Thread thread, final Throwable throwable) {
+ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable);
+ }
+ });
-+ }, (long)(20.0e6)); // 20ms
-+ WORKER_THREADS = workerThreads;
++ }
++ }
++ );
++ public static final long WORKER_QUEUE_HOLD_TIME = (long)(20.0e6); // 20ms
++ public static final int CLIENT_DIVISION = 0;
++ public static final PrioritisedThreadPool.ExecutorGroup RENDER_EXECUTOR_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(CLIENT_DIVISION, 0);
++ public static final int SERVER_DIVISION = 1;
++ public static final PrioritisedThreadPool.ExecutorGroup PARALLEL_GEN_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0);
++ public static final PrioritisedThreadPool.ExecutorGroup RADIUS_AWARE_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0);
++ public static final PrioritisedThreadPool.ExecutorGroup LOAD_GROUP = MoonriseCommon.WORKER_POOL.createExecutorGroup(SERVER_DIVISION, 0);
++
++ public static void adjustWorkerThreads(final int configWorkerThreads, final int configIoThreads) {
++ int defaultWorkerThreads = Runtime.getRuntime().availableProcessors() / 2;
++ if (defaultWorkerThreads <= 4) {
++ defaultWorkerThreads = defaultWorkerThreads <= 3 ? 1 : 2;
++ } else {
++ defaultWorkerThreads = defaultWorkerThreads / 2;
++ }
++ defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads));
++
++ int workerThreads = configWorkerThreads;
++
++ if (workerThreads <= 0) {
++ workerThreads = defaultWorkerThreads;
++ }
++
++ final int ioThreads = Math.max(1, configIoThreads);
++
++ WORKER_POOL.adjustThreadCount(workerThreads);
++ IO_POOL.adjustThreadCount(ioThreads);
++
++ LOGGER.info(PlatformHooks.get().getBrand() + " is using " + workerThreads + " worker threads, " + ioThreads + " I/O threads");
++ }
++
++ public static final PrioritisedThreadPool IO_POOL = new PrioritisedThreadPool(
++ new Consumer<>() {
++ private final AtomicInteger idGenerator = new AtomicInteger();
++
++ @Override
++ public void accept(final Thread thread) {
++ thread.setDaemon(true);
++ thread.setName(PlatformHooks.get().getBrand() + " I/O Worker #" + this.idGenerator.getAndIncrement());
++ thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
++ @Override
++ public void uncaughtException(final Thread thread, final Throwable throwable) {
++ LOGGER.error("Uncaught exception in thread " + thread.getName(), throwable);
++ }
++ });
++ }
++ }
++ );
++ public static final long IO_QUEUE_HOLD_TIME = (long)(100.0e6); // 100ms
++ public static final PrioritisedThreadPool.ExecutorGroup CLIENT_PROFILER_IO_GROUP = IO_POOL.createExecutorGroup(CLIENT_DIVISION, 0);
++ public static final PrioritisedThreadPool.ExecutorGroup SERVER_REGION_IO_GROUP = IO_POOL.createExecutorGroup(SERVER_DIVISION, 0);
++
++ public static void haltExecutors() {
++ MoonriseCommon.WORKER_POOL.shutdown(false);
++ LOGGER.info("Awaiting termination of worker pool for up to 60s...");
++ if (!MoonriseCommon.WORKER_POOL.join(TimeUnit.SECONDS.toMillis(60L))) {
++ LOGGER.error("Worker pool did not shut down in time!");
++ MoonriseCommon.WORKER_POOL.halt(false);
++ }
++
++ MoonriseCommon.IO_POOL.shutdown(false);
++ LOGGER.info("Awaiting termination of I/O pool for up to 60s...");
++ if (!MoonriseCommon.IO_POOL.join(TimeUnit.SECONDS.toMillis(60L))) {
++ LOGGER.error("I/O pool did not shut down in time!");
++ MoonriseCommon.IO_POOL.halt(false);
++ }
+ }
+
+ private MoonriseCommon() {}
+}
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java
new file mode 100644
-index 0000000000000000000000000000000000000000..1cf32d7d1bbc8a0a3f7cb9024c793f6744199f64
+index 0000000000000000000000000000000000000000..559c959aff3c9deef867b9e425fba3e2e669cac6
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/MoonriseConstants.java
-@@ -0,0 +1,9 @@
+@@ -0,0 +1,11 @@
+package ca.spottedleaf.moonrise.common.util;
+
++import ca.spottedleaf.moonrise.common.PlatformHooks;
++
+public final class MoonriseConstants {
+
-+ public static final int MAX_VIEW_DISTANCE = 32;
++ public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32);
+
+ private MoonriseConstants() {}
+
+}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java b/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..a9ff1c1a70faf4b7a64b265932f07a8b8f00c1ff
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/common/util/SimpleRandom.java
+@@ -0,0 +1,52 @@
++package ca.spottedleaf.moonrise.common.util;
++
++import net.minecraft.world.level.levelgen.LegacyRandomSource;
++
++/**
++ * Avoid costly CAS of superclass
++ */
++public final class SimpleRandom extends LegacyRandomSource {
++
++ private static final long MULTIPLIER = 25214903917L;
++ private static final long ADDEND = 11L;
++ private static final int BITS = 48;
++ private static final long MASK = (1L << BITS) - 1;
++
++ private long value;
++
++ public SimpleRandom(final long seed) {
++ super(0L);
++ this.value = seed;
++ }
++
++ @Override
++ public void setSeed(final long seed) {
++ this.value = (seed ^ MULTIPLIER) & MASK;
++ }
++
++ private long advanceSeed() {
++ return this.value = ((this.value * MULTIPLIER) + ADDEND) & MASK;
++ }
++
++ @Override
++ public int next(final int bits) {
++ return (int)(this.advanceSeed() >>> (BITS - bits));
++ }
++
++ @Override
++ public int nextInt() {
++ final long seed = this.advanceSeed();
++ return (int)(seed >>> (BITS - Integer.SIZE));
++ }
++
++ @Override
++ public int nextInt(final int bound) {
++ if (bound <= 0) {
++ throw new IllegalArgumentException();
++ }
++
++ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
++ final long value = this.advanceSeed() >>> (BITS - Integer.SIZE);
++ return (int)((value * (long)bound) >>> Integer.SIZE);
++ }
++}
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java
new file mode 100644
-index 0000000000000000000000000000000000000000..11b7f15755dde766140c29bedca456c80d53293f
+index 0000000000000000000000000000000000000000..217d1f908a36a5177ba3cbb80a33f73d4dab0fa0
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/TickThread.java
-@@ -0,0 +1,139 @@
+@@ -0,0 +1,143 @@
+package ca.spottedleaf.moonrise.common.util;
+
+import net.minecraft.core.BlockPos;
@@ -3806,11 +4180,15 @@ index 0000000000000000000000000000000000000000..11b7f15755dde766140c29bedca456c8
+ }
+
+ public TickThread(final Runnable run, final String name) {
-+ this(run, name, ID_GENERATOR.incrementAndGet());
++ this(null, run, name);
+ }
+
-+ private TickThread(final Runnable run, final String name, final int id) {
-+ super(run, name);
++ public TickThread(final ThreadGroup group, final Runnable run, final String name) {
++ this(group, run, name, ID_GENERATOR.incrementAndGet());
++ }
++
++ private TickThread(final ThreadGroup group, final Runnable run, final String name, final int id) {
++ super(group, run, name);
+ this.id = id;
+ }
+
@@ -3868,10 +4246,10 @@ index 0000000000000000000000000000000000000000..11b7f15755dde766140c29bedca456c8
+}
diff --git a/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java b/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java
new file mode 100644
-index 0000000000000000000000000000000000000000..561a1a3ff418393d0a0db58de91b336f4c33aa4e
+index 0000000000000000000000000000000000000000..efda2688ae1254a82ba7f6bf8bf597ef224cbb86
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/moonrise/common/util/WorldUtil.java
-@@ -0,0 +1,54 @@
+@@ -0,0 +1,62 @@
+package ca.spottedleaf.moonrise.common.util;
+
+import net.minecraft.world.level.Level;
@@ -3882,13 +4260,21 @@ index 0000000000000000000000000000000000000000..561a1a3ff418393d0a0db58de91b336f
+ // min, max are inclusive
+
+ public static int getMaxSection(final LevelHeightAccessor world) {
-+ return world.getMaxSectionY() - 1; // getMaxSection() is exclusive
++ return world.getMaxSectionY();
++ }
++
++ public static int getMaxSection(final Level world) {
++ return world.getMaxSectionY();
+ }
+
+ public static int getMinSection(final LevelHeightAccessor world) {
+ return world.getMinSectionY();
+ }
+
++ public static int getMinSection(final Level world) {
++ return world.getMinSectionY();
++ }
++
+ public static int getMaxLightSection(final LevelHeightAccessor world) {
+ return getMaxSection(world) + 1;
+ }
@@ -3926,6 +4312,221 @@ index 0000000000000000000000000000000000000000..561a1a3ff418393d0a0db58de91b336f
+ throw new RuntimeException();
+ }
+}
+diff --git a/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..1aa6be257ce594d7a69fdff008cd29014a04fd75
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/moonrise/paper/PaperHooks.java
+@@ -0,0 +1,209 @@
++package ca.spottedleaf.moonrise.paper;
++
++import ca.spottedleaf.moonrise.common.PlatformHooks;
++import com.mojang.datafixers.DSL;
++import com.mojang.datafixers.DataFixer;
++import com.mojang.serialization.Dynamic;
++import net.minecraft.core.BlockPos;
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.nbt.NbtOps;
++import net.minecraft.server.level.ChunkHolder;
++import net.minecraft.server.level.GenerationChunkHolder;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.level.ServerPlayer;
++import net.minecraft.world.entity.Entity;
++import net.minecraft.world.level.BlockGetter;
++import net.minecraft.world.level.ChunkPos;
++import net.minecraft.world.level.Level;
++import net.minecraft.world.level.block.state.BlockState;
++import net.minecraft.world.level.chunk.ChunkAccess;
++import net.minecraft.world.level.chunk.LevelChunk;
++import net.minecraft.world.level.chunk.ProtoChunk;
++import net.minecraft.world.level.chunk.storage.SerializableChunkData;
++import net.minecraft.world.level.entity.EntityTypeTest;
++import net.minecraft.world.phys.AABB;
++import java.util.List;
++import java.util.function.Predicate;
++
++public final class PaperHooks implements PlatformHooks {
++
++ @Override
++ public String getBrand() {
++ return "Paper";
++ }
++
++ @Override
++ public int getLightEmission(final BlockState blockState, final BlockGetter world, final BlockPos pos) {
++ return blockState.getLightEmission();
++ }
++
++ @Override
++ public Predicate maybeHasLightEmission() {
++ return (final BlockState state) -> {
++ return state.getLightEmission() != 0;
++ };
++ }
++
++ @Override
++ public boolean hasCurrentlyLoadingChunk() {
++ return false;
++ }
++
++ @Override
++ public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder) {
++ return null;
++ }
++
++ @Override
++ public void setCurrentlyLoading(final GenerationChunkHolder holder, final LevelChunk levelChunk) {
++
++ }
++
++ @Override
++ public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original) {
++
++ }
++
++ @Override
++ public boolean allowAsyncTicketUpdates() {
++ return true;
++ }
++
++ @Override
++ public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) {
++
++ }
++
++ @Override
++ public void chunkUnloadFromWorld(final LevelChunk chunk) {
++
++ }
++
++ @Override
++ public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) {
++
++ }
++
++ @Override
++ public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player) {
++
++ }
++
++ @Override
++ public void onChunkUnWatch(final ServerLevel world, final ChunkPos chunk, final ServerPlayer player) {
++
++ }
++
++ @Override
++ public void addToGetEntities(final Level world, final Entity entity, final AABB boundingBox, final Predicate super Entity> predicate, final List into) {
++
++ }
++
++ @Override
++ public void addToGetEntities(final Level world, final EntityTypeTest entityTypeTest, final AABB boundingBox, final Predicate super T> predicate, final List super T> into, final int maxCount) {
++
++ }
++
++ @Override
++ public void entityMove(final Entity entity, final long oldSection, final long newSection) {
++
++ }
++
++ @Override
++ public boolean screenEntity(final ServerLevel world, final Entity entity, final boolean fromDisk, final boolean event) {
++ return true;
++ }
++
++ @Override
++ public boolean configFixMC224294() {
++ return true;
++ }
++
++ @Override
++ public boolean configAutoConfigSendDistance() {
++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance;
++ }
++
++ @Override
++ public double configPlayerMaxLoadRate() {
++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate;
++ }
++
++ @Override
++ public double configPlayerMaxGenRate() {
++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate;
++ }
++
++ @Override
++ public double configPlayerMaxSendRate() {
++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate;
++ }
++
++ @Override
++ public int configPlayerMaxConcurrentLoads() {
++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads;
++ }
++
++ @Override
++ public int configPlayerMaxConcurrentGens() {
++ return io.papermc.paper.configuration.GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates;
++ }
++
++ @Override
++ public long configAutoSaveInterval(final ServerLevel world) {
++ return world.paperConfig().chunks.autoSaveInterval.value();
++ }
++
++ @Override
++ public int configMaxAutoSavePerTick(final ServerLevel world) {
++ return world.paperConfig().chunks.maxAutoSaveChunksPerTick;
++ }
++
++ @Override
++ public boolean configFixMC159283() {
++ return true;
++ }
++
++ @Override
++ public boolean forceNoSave(final ChunkAccess chunk) {
++ return chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave;
++ }
++
++ @Override
++ public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
++ final int fromVersion, final int toVersion) {
++ return (CompoundTag)dataFixer.update(
++ type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion
++ ).getValue();
++ }
++
++ @Override
++ public boolean hasMainChunkLoadHook() {
++ return false;
++ }
++
++ @Override
++ public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) {
++
++ }
++
++ @Override
++ public List modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List entities) {
++ return entities;
++ }
++
++ @Override
++ public void unloadEntity(final Entity entity) {
++ entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK, org.bukkit.event.entity.EntityRemoveEvent.Cause.UNLOAD);
++ }
++
++ @Override
++ public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) {
++ net.minecraft.world.level.chunk.status.ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities());
++ }
++
++ @Override
++ public int modifyEntityTrackingRange(final Entity entity, final int currentRange) {
++ return org.spigotmc.TrackingRange.getEntityTrackingRange(entity, currentRange);
++ }
++}
diff --git a/src/main/java/com/mojang/logging/LogUtils.java b/src/main/java/com/mojang/logging/LogUtils.java
index 46cab7a8c7b87ab01b26074b04f5a02b3907cfc4..49019b4a9bc4e634d54a9b0acaf9229a5c896f85 100644
--- a/src/main/java/com/mojang/logging/LogUtils.java
@@ -3940,6 +4541,19 @@ index 46cab7a8c7b87ab01b26074b04f5a02b3907cfc4..49019b4a9bc4e634d54a9b0acaf9229a
+ }
+ // Paper end
}
+diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
+index 7e88b1fc1ff700a7771b38f139f4472eaeaf8714..904d2f96a60e72aa089fdfe6be08044b04f995c1 100644
+--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
++++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
+@@ -242,7 +242,7 @@ public class GlobalConfiguration extends ConfigurationPart {
+
+ @PostProcess
+ private void postProcess() {
+-
++ ca.spottedleaf.moonrise.common.util.MoonriseCommon.adjustWorkerThreads(this.workerThreads, this.ioThreads);
+ }
+ }
+
diff --git a/src/main/java/io/papermc/paper/util/IntervalledCounter.java b/src/main/java/io/papermc/paper/util/IntervalledCounter.java
new file mode 100644
index 0000000000000000000000000000000000000000..197224e31175252d8438a8df585bbb65f2288d7f
@@ -4822,7 +5436,7 @@ index 9cdcab885a915990a679f3fc9ae6885f7d125bfd..c615510f3f59292715bcff1bd9e4e896
if (!this.level.shouldTickBlocksAt(pos)) {
return false;
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index f6a3606b972064c4ec78487374e6197c0c447e27..6fe373de360570b528b8133043ef3bb9ba12529d 100644
+index f6a3606b972064c4ec78487374e6197c0c447e27..c6ded1ac73ddbc0336000f77c0f99fa20551a0de 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -239,6 +239,98 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -4859,7 +5473,7 @@ index f6a3606b972064c4ec78487374e6197c0c447e27..6fe373de360570b528b8133043ef3bb9
+ return true;
+ }
+
-+ public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority,
++ public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.util.Priority priority,
+ java.util.function.Consumer> onLoad) {
+ if (Thread.currentThread() != this.thread) {
+ this.getChunkSource().mainThreadProcessor.execute(() -> {
@@ -5513,24 +6127,35 @@ index 8bd5fb4971be46a51534c202e10a362723ad8664..5321109ca638036572df9a7e17eafcef
@Override
public BlockState getBlockState(BlockPos pos) {
int i = pos.getY();
+diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
+index 3f552ee8f90566edddb5943311a14309e4bebb61..412caefe776df6c8e931f6a1a48ea2525ec6610e 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
++++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
+@@ -168,7 +168,7 @@ public class ChunkStatusTasks {
+ }, context.mainThreadExecutor());
+ }
+
+- private static void postLoadProtoChunk(ServerLevel world, List entities) {
++ public static void postLoadProtoChunk(ServerLevel world, List entities) { // Paper - public
+ if (!entities.isEmpty()) {
+ // CraftBukkit start - these are spawned serialized (DefinedStructure) and we don't call an add event below at the moment due to ordering complexities
+ world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(entities, world, EntitySpawnReason.LOAD).filter((entity) -> {
diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
-index 34933c5324126f9afdc5cba9dea997ace8f01806..1cfc906317f07a44f06a4adf021c44e34a2f1d07 100644
+index 34933c5324126f9afdc5cba9dea997ace8f01806..4eb0b0969325f39a7ae65492cccd482515a50142 100644
--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
-@@ -91,6 +91,18 @@ public class PersistentEntitySectionManager implements A
+@@ -91,6 +91,16 @@ public class PersistentEntitySectionManager implements A
}
private boolean addEntity(T entity, boolean existing) {
+ // Paper start - chunk system hooks
-+ if (existing) {
-+ // I don't want to know why this is a generic type.
-+ Entity entityCasted = (Entity)entity;
-+ boolean wasRemoved = entityCasted.isRemoved();
-+ boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted);
-+ if ((!wasRemoved && entityCasted.isRemoved()) || !screened) {
-+ // removed by callback
-+ return false;
-+ }
++ // I don't want to know why this is a generic type.
++ Entity entityCasted = (Entity)entity;
++ boolean wasRemoved = entityCasted.isRemoved();
++ boolean screened = ca.spottedleaf.moonrise.common.util.ChunkSystem.screenEntity((net.minecraft.server.level.ServerLevel)entityCasted.level(), entityCasted, existing, true);
++ if ((!wasRemoved && entityCasted.isRemoved()) || !screened) {
++ // removed by callback
++ return false;
+ }
+ // Paper end - chunk system hooks
if (!this.addEntityUuid(entity)) {
@@ -5551,7 +6176,7 @@ index 3882ae04173cd125fe490692a6bc2b4d8b20ff7b..eb61712ea067b277e7f32f887e3528fa
+ }
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 799444e4101283c972a160742a9e2548e604173f..456c58877490feab6be3577d34c89ea776568617 100644
+index 799444e4101283c972a160742a9e2548e604173f..8b58884d6cb1088a2fffb36a99bfe4dc568326d1 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -256,8 +256,8 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@@ -5587,11 +6212,11 @@ index 799444e4101283c972a160742a9e2548e604173f..456c58877490feab6be3577d34c89ea7
+ }
+ }
+
-+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority;
++ ca.spottedleaf.concurrentutil.util.Priority priority;
+ if (urgent) {
-+ priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER;
++ priority = ca.spottedleaf.concurrentutil.util.Priority.HIGHER;
+ } else {
-+ priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL;
++ priority = ca.spottedleaf.concurrentutil.util.Priority.NORMAL;
+ }
+
+ java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>();
@@ -5774,3 +6399,10 @@ index 1bf87b4915edf341ad55f8274cef324e0bc28547..3591b79481ac17bd02e59ac3c623d1c6
MONSTER,
ANIMAL,
RAIDER,
+diff --git a/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks
+new file mode 100644
+index 0000000000000000000000000000000000000000..e57c3ca79677b1dfe7cf3db36f0406de7ea5bd0a
+--- /dev/null
++++ b/src/main/resources/META-INF/services/ca.spottedleaf.moonrise.common.PlatformHooks
+@@ -0,0 +1 @@
++ca.spottedleaf.moonrise.paper.PaperHooks
diff --git a/patches/server/0010-Adventure.patch b/patches/server/0010-Adventure.patch
index a8d9cacd74..7375271d20 100644
--- a/patches/server/0010-Adventure.patch
+++ b/patches/server/0010-Adventure.patch
@@ -3558,7 +3558,7 @@ index cbdb1a56a97150c164515a4ce6d3ba06428bf321..b214e7b302abbfe1641485a05f1371ac
public URI getUrl() {
return this.handle.link();
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 456c58877490feab6be3577d34c89ea776568617..274c9cc32d7f65456d184db7f61bc4b159f890f8 100644
+index 8b58884d6cb1088a2fffb36a99bfe4dc568326d1..9a79b948264150d0f7a843a8ddd2ea9245ae66f3 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -166,6 +166,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0023-Timings-v2.patch b/patches/server/0023-Timings-v2.patch
index 4e5f30f3df..6ac52a48c7 100644
--- a/patches/server/0023-Timings-v2.patch
+++ b/patches/server/0023-Timings-v2.patch
@@ -1118,7 +1118,7 @@ index c615510f3f59292715bcff1bd9e4e896c9733436..93422468474189343cdc1e29f06f6dfb
}
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 6fe373de360570b528b8133043ef3bb9ba12529d..d82f4255faac84ce6af47e86707f5c035529ab5d 100644
+index c6ded1ac73ddbc0336000f77c0f99fa20551a0de..a1565304c436258b561d97a6cc7aacecf68816b7 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1,6 +1,8 @@
diff --git a/patches/server/0036-Entity-Origin-API.patch b/patches/server/0036-Entity-Origin-API.patch
index 0762019eff..e1281dbfae 100644
--- a/patches/server/0036-Entity-Origin-API.patch
+++ b/patches/server/0036-Entity-Origin-API.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Entity Origin API
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index d82f4255faac84ce6af47e86707f5c035529ab5d..a31371dd479f5d87ff62728504563815bb5e22f8 100644
+index a1565304c436258b561d97a6cc7aacecf68816b7..e2e949dbd8be52dc6a00414f0f79746ee554acd7 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -2212,6 +2212,15 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0045-Disable-thunder.patch b/patches/server/0045-Disable-thunder.patch
index ab1379c28e..b0c60085d2 100644
--- a/patches/server/0045-Disable-thunder.patch
+++ b/patches/server/0045-Disable-thunder.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Disable thunder
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index a31371dd479f5d87ff62728504563815bb5e22f8..f1145c190b8cb50a800d9324b25aa884b7de9606 100644
+index e2e949dbd8be52dc6a00414f0f79746ee554acd7..1e012aef9a515b2e312565e8fe7652f38e080e4b 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -603,7 +603,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0046-Disable-ice-and-snow.patch b/patches/server/0046-Disable-ice-and-snow.patch
index 7f43a10174..8ba40908f9 100644
--- a/patches/server/0046-Disable-ice-and-snow.patch
+++ b/patches/server/0046-Disable-ice-and-snow.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Disable ice and snow
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index f1145c190b8cb50a800d9324b25aa884b7de9606..77d61f16b5b45fbe78deaf90f4ae4b126546c3e9 100644
+index 1e012aef9a515b2e312565e8fe7652f38e080e4b..821df759e3410428b4558e22042c79f29238ebe2 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -633,11 +633,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0076-Configurable-spawn-chances-for-skeleton-horses.patch b/patches/server/0076-Configurable-spawn-chances-for-skeleton-horses.patch
index 3a7066283f..2a2c4f2b31 100644
--- a/patches/server/0076-Configurable-spawn-chances-for-skeleton-horses.patch
+++ b/patches/server/0076-Configurable-spawn-chances-for-skeleton-horses.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Configurable spawn chances for skeleton horses
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 77d61f16b5b45fbe78deaf90f4ae4b126546c3e9..63f99fb1dde13854ad9be1a57441bd1b1a40c8d6 100644
+index 821df759e3410428b4558e22042c79f29238ebe2..b80af01529206bf55ed028dac41798f91da22f51 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -608,7 +608,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0077-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch b/patches/server/0077-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch
index 07883924de..426276647c 100644
--- a/patches/server/0077-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch
+++ b/patches/server/0077-Only-process-BlockPhysicsEvent-if-a-plugin-has-a-lis.patch
@@ -18,7 +18,7 @@ index 254e34f80ed0d06f200a78c60f34b4ffc5e5ed85..b8a60520cc10383f22d810ca8061ed25
gameprofilerfiller.push(() -> {
String s = String.valueOf(worldserver);
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 63f99fb1dde13854ad9be1a57441bd1b1a40c8d6..9aa60bb30ce5f2deddcb8b228ebafac766f02f93 100644
+index b80af01529206bf55ed028dac41798f91da22f51..237a0b4dd0e1c437a021e1d2104e6d523b1f745a 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -230,6 +230,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0078-Entity-AddTo-RemoveFrom-World-Events.patch b/patches/server/0078-Entity-AddTo-RemoveFrom-World-Events.patch
index 5171c96a52..e529f629a6 100644
--- a/patches/server/0078-Entity-AddTo-RemoveFrom-World-Events.patch
+++ b/patches/server/0078-Entity-AddTo-RemoveFrom-World-Events.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Entity AddTo/RemoveFrom World Events
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 9aa60bb30ce5f2deddcb8b228ebafac766f02f93..d773a56d2bea90669f0b65908d990c2fc5313652 100644
+index 237a0b4dd0e1c437a021e1d2104e6d523b1f745a..6ec83b72bb9f762d606fcbf4b93c70f2b025f48f 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -2224,6 +2224,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0085-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch b/patches/server/0085-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch
index a2348e6b77..f087a2a35f 100644
--- a/patches/server/0085-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch
+++ b/patches/server/0085-Fix-Cancelling-BlockPlaceEvent-triggering-physics.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Fix Cancelling BlockPlaceEvent triggering physics
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index d773a56d2bea90669f0b65908d990c2fc5313652..8dc2b2d8ba32aefc11eb23054b902650fac76adf 100644
+index 6ec83b72bb9f762d606fcbf4b93c70f2b025f48f..5a9c703a252d0c1c57c23ef021e57cdd1de31585 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1402,11 +1402,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0101-Fix-global-sound-handling.patch b/patches/server/0101-Fix-global-sound-handling.patch
index e96dd9f673..1c4cad79e3 100644
--- a/patches/server/0101-Fix-global-sound-handling.patch
+++ b/patches/server/0101-Fix-global-sound-handling.patch
@@ -11,7 +11,7 @@ Co-authored-by: lexikiq
Co-authored-by: Aikar
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 8dc2b2d8ba32aefc11eb23054b902650fac76adf..21c3d771a3dd921767c2cba1e11583d015879ca9 100644
+index 5a9c703a252d0c1c57c23ef021e57cdd1de31585..022ec2ff1c5a6a1de867b2a6dafb339d55a0905d 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1335,7 +1335,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0123-Provide-E-TE-Chunk-count-stat-methods.patch b/patches/server/0123-Provide-E-TE-Chunk-count-stat-methods.patch
index 00007012a2..ad688961b8 100644
--- a/patches/server/0123-Provide-E-TE-Chunk-count-stat-methods.patch
+++ b/patches/server/0123-Provide-E-TE-Chunk-count-stat-methods.patch
@@ -20,7 +20,7 @@ index 658ceb9c43bb48f88596cd7270e276a369a3937e..b34663ba24a0925c7fe65b354f4029c5
private final List pendingBlockEntityTickers = Lists.newArrayList();
private boolean tickingBlockEntities;
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 274c9cc32d7f65456d184db7f61bc4b159f890f8..8cf4ecc5065ade18f0d13eaf0a786912ecb4dd08 100644
+index 9a79b948264150d0f7a843a8ddd2ea9245ae66f3..9389c0ebdcaba5022bdae47b2b2ff06b40406127 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -168,6 +168,56 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0186-Expand-World.spawnParticle-API-and-add-Builder.patch b/patches/server/0186-Expand-World.spawnParticle-API-and-add-Builder.patch
index bc692b5fa6..8fcc3562f2 100644
--- a/patches/server/0186-Expand-World.spawnParticle-API-and-add-Builder.patch
+++ b/patches/server/0186-Expand-World.spawnParticle-API-and-add-Builder.patch
@@ -10,7 +10,7 @@ Adds an option to control the force mode of the particle.
This adds a new Builder API which is much friendlier to use.
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 21c3d771a3dd921767c2cba1e11583d015879ca9..252f1deea98a6eb83ac99adea3ea7a3c229c0f07 100644
+index 022ec2ff1c5a6a1de867b2a6dafb339d55a0905d..17cb827bc90d980d73719776fca967a1d307ce0a 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1566,12 +1566,17 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -34,7 +34,7 @@ index 21c3d771a3dd921767c2cba1e11583d015879ca9..252f1deea98a6eb83ac99adea3ea7a3c
if (this.sendParticles(entityplayer, force, d0, d1, d2, packetplayoutworldparticles)) { // CraftBukkit
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 8cf4ecc5065ade18f0d13eaf0a786912ecb4dd08..4b2742c0841bd686ee637e1d6dc011e13b0df192 100644
+index 9389c0ebdcaba5022bdae47b2b2ff06b40406127..5231548e886e884f565ff6cc5d45518141fbab2d 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -1992,8 +1992,19 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0202-Expand-Explosions-API.patch b/patches/server/0202-Expand-Explosions-API.patch
index b454b3ebcf..b9a5452eb8 100644
--- a/patches/server/0202-Expand-Explosions-API.patch
+++ b/patches/server/0202-Expand-Explosions-API.patch
@@ -9,7 +9,7 @@ Co-authored-by: Esoteric Enderman <90862990+EsotericEnderman@users.noreply.githu
Co-authored-by: Bjarne Koll
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 252f1deea98a6eb83ac99adea3ea7a3c229c0f07..a4709b08ea90e15533a3e1b1d22d16e004556243 100644
+index 17cb827bc90d980d73719776fca967a1d307ce0a..6283f3496f122d4b0c4ac297943baf469e44aee3 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1449,6 +1449,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -54,7 +54,7 @@ index 9f37d7284c81d529551107e2836627977efabd65..d1878f597c3d8119e9b248f4fe8af435
while (iterator.hasNext()) {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 4b2742c0841bd686ee637e1d6dc011e13b0df192..22543e032f9202d590ac3924b537bb43efba9c93 100644
+index 5231548e886e884f565ff6cc5d45518141fbab2d..e285c8486c36e8d2bc4ddc43e0029943ea5c7fe7 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -784,6 +784,11 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0206-Implement-World.getEntity-UUID-API.patch b/patches/server/0206-Implement-World.getEntity-UUID-API.patch
index cb1b631e5d..5e9929379a 100644
--- a/patches/server/0206-Implement-World.getEntity-UUID-API.patch
+++ b/patches/server/0206-Implement-World.getEntity-UUID-API.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Implement World.getEntity(UUID) API
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 22543e032f9202d590ac3924b537bb43efba9c93..1982385aa0e4984544d2aef88f5cafd5c0d5a49a 100644
+index e285c8486c36e8d2bc4ddc43e0029943ea5c7fe7..e9840a1159419593145d166b54e523fd3e6684f0 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -1137,6 +1137,15 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0207-InventoryCloseEvent-Reason-API.patch b/patches/server/0207-InventoryCloseEvent-Reason-API.patch
index d0f2f2b3fc..968d1a87f8 100644
--- a/patches/server/0207-InventoryCloseEvent-Reason-API.patch
+++ b/patches/server/0207-InventoryCloseEvent-Reason-API.patch
@@ -7,7 +7,7 @@ Allows you to determine why an inventory was closed, enabling plugin developers
to "confirm" things based on if it was player triggered close or not.
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index a4709b08ea90e15533a3e1b1d22d16e004556243..438c936fceede5b21435e1f37f2372072a9d4571 100644
+index 6283f3496f122d4b0c4ac297943baf469e44aee3..425c22fe94e83d880b331cbfb16dc67f22def1c7 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1234,7 +1234,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0224-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch b/patches/server/0224-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch
index c1032d9949..4338c3faf2 100644
--- a/patches/server/0224-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch
+++ b/patches/server/0224-Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Add Debug Entities option to debug dupe uuid issues
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 438c936fceede5b21435e1f37f2372072a9d4571..3f4d3e2f45c2b2228a333076ec1f34228560593e 100644
+index 425c22fe94e83d880b331cbfb16dc67f22def1c7..a12d5de8834bbc3176be3d2c8353b2d4372dbc1d 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1196,6 +1196,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0229-Optimize-BlockPosition-helper-methods.patch b/patches/server/0229-Optimize-BlockPosition-helper-methods.patch
index 44418eff5a..5c3a739bd0 100644
--- a/patches/server/0229-Optimize-BlockPosition-helper-methods.patch
+++ b/patches/server/0229-Optimize-BlockPosition-helper-methods.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Optimize BlockPosition helper methods
diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java
-index 83e7c141d947f8f8096fed1da716560494bc5c62..eea8bea0f40db8d36c59e628babf788fa920df94 100644
+index 83e7c141d947f8f8096fed1da716560494bc5c62..8e00fd1a1fb0d73e800395f4b9fffdbdb6c6b5cb 100644
--- a/src/main/java/net/minecraft/core/BlockPos.java
+++ b/src/main/java/net/minecraft/core/BlockPos.java
@@ -157,67 +157,84 @@ public class BlockPos extends Vec3i {
@@ -19,7 +19,7 @@ index 83e7c141d947f8f8096fed1da716560494bc5c62..eea8bea0f40db8d36c59e628babf788f
@Override
public BlockPos above(int distance) {
- return this.relative(Direction.UP, distance);
-+ return distance == 0 ? this : new BlockPos(this.getX(), this.getY() + distance, this.getZ()); // Paper - Perf: Optimize BlockPosition
++ return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY() + distance, this.getZ()); // Paper - Perf: Optimize BlockPosition
}
@Override
@@ -31,7 +31,7 @@ index 83e7c141d947f8f8096fed1da716560494bc5c62..eea8bea0f40db8d36c59e628babf788f
@Override
public BlockPos below(int i) {
- return this.relative(Direction.DOWN, i);
-+ return i == 0 ? this : new BlockPos(this.getX(), this.getY() - i, this.getZ()); // Paper - Perf: Optimize BlockPosition
++ return i == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY() - i, this.getZ()); // Paper - Perf: Optimize BlockPosition
}
@Override
@@ -43,7 +43,7 @@ index 83e7c141d947f8f8096fed1da716560494bc5c62..eea8bea0f40db8d36c59e628babf788f
@Override
public BlockPos north(int distance) {
- return this.relative(Direction.NORTH, distance);
-+ return distance == 0 ? this : new BlockPos(this.getX(), this.getY(), this.getZ() - distance); // Paper - Perf: Optimize BlockPosition
++ return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY(), this.getZ() - distance); // Paper - Perf: Optimize BlockPosition
}
@Override
@@ -55,7 +55,7 @@ index 83e7c141d947f8f8096fed1da716560494bc5c62..eea8bea0f40db8d36c59e628babf788f
@Override
public BlockPos south(int distance) {
- return this.relative(Direction.SOUTH, distance);
-+ return distance == 0 ? this : new BlockPos(this.getX(), this.getY(), this.getZ() + distance); // Paper - Perf: Optimize BlockPosition
++ return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY(), this.getZ() + distance); // Paper - Perf: Optimize BlockPosition
}
@Override
@@ -67,7 +67,7 @@ index 83e7c141d947f8f8096fed1da716560494bc5c62..eea8bea0f40db8d36c59e628babf788f
@Override
public BlockPos west(int distance) {
- return this.relative(Direction.WEST, distance);
-+ return distance == 0 ? this : new BlockPos(this.getX() - distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition
++ return distance == 0 ? this.immutable() : new BlockPos(this.getX() - distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition
}
@Override
@@ -79,7 +79,7 @@ index 83e7c141d947f8f8096fed1da716560494bc5c62..eea8bea0f40db8d36c59e628babf788f
@Override
public BlockPos east(int distance) {
- return this.relative(Direction.EAST, distance);
-+ return distance == 0 ? this : new BlockPos(this.getX() + distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition
++ return distance == 0 ? this.immutable() : new BlockPos(this.getX() + distance, this.getY(), this.getZ()); // Paper - Perf: Optimize BlockPosition
}
@Override
diff --git a/patches/server/0238-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch b/patches/server/0238-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch
index 79a35711a9..bb06c999cf 100644
--- a/patches/server/0238-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch
+++ b/patches/server/0238-Make-CraftWorld-loadChunk-int-int-false-load-unconve.patch
@@ -6,7 +6,7 @@ Subject: [PATCH] Make CraftWorld#loadChunk(int, int, false) load unconverted
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 1982385aa0e4984544d2aef88f5cafd5c0d5a49a..0a53b01094bd8070e57fb3c967c1129a53bd7ff8 100644
+index e9840a1159419593145d166b54e523fd3e6684f0..a8e738941d8cd6373eb0ae5259da8e763a695657 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -438,7 +438,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0252-Add-sun-related-API.patch b/patches/server/0252-Add-sun-related-API.patch
index 63382c55de..2c323e679f 100644
--- a/patches/server/0252-Add-sun-related-API.patch
+++ b/patches/server/0252-Add-sun-related-API.patch
@@ -7,7 +7,7 @@ Subject: [PATCH] Add sun related API
public net.minecraft.world.entity.Mob isSunBurnTick()Z
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 0a53b01094bd8070e57fb3c967c1129a53bd7ff8..d723486f1582b232fbf78898a56caa3a13954417 100644
+index a8e738941d8cd6373eb0ae5259da8e763a695657..d5f8c07480b1dc6b6f97e8ebc74b50493f011f45 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -762,6 +762,13 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0278-Fixes-and-additions-to-the-spawn-reason-API.patch b/patches/server/0278-Fixes-and-additions-to-the-spawn-reason-API.patch
index 2e0766fbd7..206e8d576d 100644
--- a/patches/server/0278-Fixes-and-additions-to-the-spawn-reason-API.patch
+++ b/patches/server/0278-Fixes-and-additions-to-the-spawn-reason-API.patch
@@ -26,7 +26,7 @@ index f55832ce841621daab4d3a910650ab6562cefcda..f635da34335cd2901adf975fcd74c5c6
});
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 3f4d3e2f45c2b2228a333076ec1f34228560593e..64c1fd62a865adb1e11edd326a1a5ccdc98f13ed 100644
+index a12d5de8834bbc3176be3d2c8353b2d4372dbc1d..1976acc359d03447507b32c9032700f4603628f0 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1202,6 +1202,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0297-Duplicate-UUID-Resolve-Option.patch b/patches/server/0297-Duplicate-UUID-Resolve-Option.patch
index 502d6dcc28..b42bece1fc 100644
--- a/patches/server/0297-Duplicate-UUID-Resolve-Option.patch
+++ b/patches/server/0297-Duplicate-UUID-Resolve-Option.patch
@@ -33,7 +33,7 @@ But for those who are ok with leaving this inconsistent behavior, you may use WA
It is recommended you regenerate the entities, as these were legit entities, and deserve your love.
diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
-index 3f552ee8f90566edddb5943311a14309e4bebb61..f18c2b85ed9541f646f157184221e333d0ae58bd 100644
+index 412caefe776df6c8e931f6a1a48ea2525ec6610e..aff4c3d63a97d5bbde004a616f7e14fca59b5ab9 100644
--- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
+++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
@@ -178,10 +178,51 @@ public class ChunkStatusTasks {
diff --git a/patches/server/0301-Optimise-EntityGetter-getPlayerByUUID.patch b/patches/server/0301-Optimise-EntityGetter-getPlayerByUUID.patch
index e0992abebb..30f3d7dfdc 100644
--- a/patches/server/0301-Optimise-EntityGetter-getPlayerByUUID.patch
+++ b/patches/server/0301-Optimise-EntityGetter-getPlayerByUUID.patch
@@ -6,7 +6,7 @@ Subject: [PATCH] Optimise EntityGetter#getPlayerByUUID
Use the PlayerList map instead of iterating over all players
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 64c1fd62a865adb1e11edd326a1a5ccdc98f13ed..c7a1328e6642ad6c76179bddec117c4281577e86 100644
+index 1976acc359d03447507b32c9032700f4603628f0..a0a38a78987f05fe47b5a85a49659e7469674210 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -333,6 +333,15 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0310-Add-debug-for-sync-chunk-loads.patch b/patches/server/0310-Add-debug-for-sync-chunk-loads.patch
index a27b5deba2..3bdd0ab20d 100644
--- a/patches/server/0310-Add-debug-for-sync-chunk-loads.patch
+++ b/patches/server/0310-Add-debug-for-sync-chunk-loads.patch
@@ -314,7 +314,7 @@ index 9ab43b4273975d7599f8eee2f95773f2984b7c37..350bfa9c891130b1aa2ab973e86668de
chunkproviderserver_b.managedBlock(completablefuture::isDone);
this.level.timings.syncChunkLoad.stopTiming(); // Paper
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index c7a1328e6642ad6c76179bddec117c4281577e86..e2984ccee4e01b260109d038969f9ff3ea21b87f 100644
+index a0a38a78987f05fe47b5a85a49659e7469674210..6e3765b00272548318fca647a80c56ad634b7ecc 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -412,6 +412,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0328-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch b/patches/server/0328-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch
index 3a9e4f516e..9607433b9d 100644
--- a/patches/server/0328-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch
+++ b/patches/server/0328-Prevent-Double-PlayerChunkMap-adds-crashing-server.patch
@@ -25,7 +25,7 @@ index f8decd2f1841da947a3cff5b275efff63ba37def..926e1ebfe3a011a28fb82b855511aaab
EntityType> entitytypes = entity.getType();
int i = entitytypes.clientTrackingRange() * 16;
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index e2984ccee4e01b260109d038969f9ff3ea21b87f..090d2fde891346ee634a8964b562715f4dd206d0 100644
+index 6e3765b00272548318fca647a80c56ad634b7ecc..609c2e1946c86deabb885d7d703cf42273839f1e 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -2232,7 +2232,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0370-Fix-Per-World-Difficulty-Remembering-Difficulty.patch b/patches/server/0370-Fix-Per-World-Difficulty-Remembering-Difficulty.patch
index 06d3e294ce..df2237cf9a 100644
--- a/patches/server/0370-Fix-Per-World-Difficulty-Remembering-Difficulty.patch
+++ b/patches/server/0370-Fix-Per-World-Difficulty-Remembering-Difficulty.patch
@@ -104,7 +104,7 @@ index f80164bf8fde13a8702f09e1e1d8aab3ba075e42..acf213f47aea597824e2301e422435fb
for (SpawnCategory spawnCategory : SpawnCategory.values()) {
if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index d723486f1582b232fbf78898a56caa3a13954417..e558ce7fddf6835dd78391b2fbc05ee22af059bd 100644
+index d5f8c07480b1dc6b6f97e8ebc74b50493f011f45..84028e959f697fade97640b9b35e5433fcf894ce 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -1177,7 +1177,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0373-Add-Plugin-Tickets-to-API-Chunk-Methods.patch b/patches/server/0373-Add-Plugin-Tickets-to-API-Chunk-Methods.patch
index e6e5474ade..1d3e93eeab 100644
--- a/patches/server/0373-Add-Plugin-Tickets-to-API-Chunk-Methods.patch
+++ b/patches/server/0373-Add-Plugin-Tickets-to-API-Chunk-Methods.patch
@@ -44,7 +44,7 @@ index acf213f47aea597824e2301e422435fbb77098fb..dfe625f406ac323ede270d3773e71b09
this.printSaveWarning = false;
this.console.autosavePeriod = this.configuration.getInt("ticks-per.autosave");
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index e558ce7fddf6835dd78391b2fbc05ee22af059bd..3ea157df3df6f590195191f2c24faead703a0bf8 100644
+index 84028e959f697fade97640b9b35e5433fcf894ce..55daddd0d80a18a3c044f48a47a0a13ef3a68943 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -270,7 +270,13 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0392-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch b/patches/server/0392-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch
index 44e149484c..1f69764500 100644
--- a/patches/server/0392-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch
+++ b/patches/server/0392-Fix-SpawnChangeEvent-not-firing-for-all-use-cases.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Fix SpawnChangeEvent not firing for all use-cases
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 090d2fde891346ee634a8964b562715f4dd206d0..465b6a1bbdbb193c6ecf5fd1598e1e4a4f8dbbbc 100644
+index 609c2e1946c86deabb885d7d703cf42273839f1e..97ce1e1d877d34581f0475066f604d5a1f349d90 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1730,7 +1730,9 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -19,7 +19,7 @@ index 090d2fde891346ee634a8964b562715f4dd206d0..465b6a1bbdbb193c6ecf5fd1598e1e4a
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 3ea157df3df6f590195191f2c24faead703a0bf8..04ef1c3433d3ac8a58f0d460877e176668e0fc8f 100644
+index 55daddd0d80a18a3c044f48a47a0a13ef3a68943..b0fbb828306194fd91f0e62244d5db6404b4f10a 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -250,12 +250,14 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0407-Extend-block-drop-capture-to-capture-all-items-added.patch b/patches/server/0407-Extend-block-drop-capture-to-capture-all-items-added.patch
index 12a14918c3..29e625b195 100644
--- a/patches/server/0407-Extend-block-drop-capture-to-capture-all-items-added.patch
+++ b/patches/server/0407-Extend-block-drop-capture-to-capture-all-items-added.patch
@@ -6,7 +6,7 @@ Subject: [PATCH] Extend block drop capture to capture all items added to the
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 465b6a1bbdbb193c6ecf5fd1598e1e4a4f8dbbbc..a3ce6feff0826dcfb2a11b0f6cd01a5368c2de3a 100644
+index 97ce1e1d877d34581f0475066f604d5a1f349d90..ca1c8cc67fe00dc7ad5e942a387f95cbad415e86 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1223,6 +1223,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0456-Add-WorldGameRuleChangeEvent.patch b/patches/server/0456-Add-WorldGameRuleChangeEvent.patch
index 7925a1810a..b03d1f41d6 100644
--- a/patches/server/0456-Add-WorldGameRuleChangeEvent.patch
+++ b/patches/server/0456-Add-WorldGameRuleChangeEvent.patch
@@ -64,7 +64,7 @@ index 7ea92a0b0f5d4eb6bd873e61c42bc0499d5d2028..09299e45552eb998fd02123c3921c065
public int get() {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 04ef1c3433d3ac8a58f0d460877e176668e0fc8f..5541a9f197777124dd908e3f56050b49df821acc 100644
+index b0fbb828306194fd91f0e62244d5db6404b4f10a..e19e5f1223f8b4fae9292a936dd05b9170610134 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -1899,9 +1899,14 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0464-Remove-stale-POIs.patch b/patches/server/0464-Remove-stale-POIs.patch
index 70792fa6b3..1574ebae11 100644
--- a/patches/server/0464-Remove-stale-POIs.patch
+++ b/patches/server/0464-Remove-stale-POIs.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Remove stale POIs
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index a3ce6feff0826dcfb2a11b0f6cd01a5368c2de3a..d62d6a345837e1b63c1a1393f18e367ac0ef4c30 100644
+index ca1c8cc67fe00dc7ad5e942a387f95cbad415e86..840651df480dfc6e494240aaa46ea4063171c0de 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1805,6 +1805,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0480-Add-EntityMoveEvent.patch b/patches/server/0480-Add-EntityMoveEvent.patch
index 7de1d3e93c..a37da973da 100644
--- a/patches/server/0480-Add-EntityMoveEvent.patch
+++ b/patches/server/0480-Add-EntityMoveEvent.patch
@@ -17,7 +17,7 @@ index c7fde30d2ea24a78d177ee38a121f02c2c71b141..223123bf689dc62abaff8086447e9258
gameprofilerfiller.push(() -> {
String s = String.valueOf(worldserver);
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index d62d6a345837e1b63c1a1393f18e367ac0ef4c30..b91ed08e8d9fbd399834d19ef01ebe5692d1ee4a 100644
+index 840651df480dfc6e494240aaa46ea4063171c0de..12f46c94710830f464ff27a3d415140c36065e10 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -231,6 +231,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0481-added-option-to-disable-pathfinding-updates-on-block.patch b/patches/server/0481-added-option-to-disable-pathfinding-updates-on-block.patch
index 3850327834..c2bfd19f14 100644
--- a/patches/server/0481-added-option-to-disable-pathfinding-updates-on-block.patch
+++ b/patches/server/0481-added-option-to-disable-pathfinding-updates-on-block.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] added option to disable pathfinding updates on block changes
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index b91ed08e8d9fbd399834d19ef01ebe5692d1ee4a..d696ec118367f64fa7151189a4ace58287329851 100644
+index 12f46c94710830f464ff27a3d415140c36065e10..8352e42a3dceb33477c5885433d24840e1332b87 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1387,6 +1387,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0512-More-World-API.patch b/patches/server/0512-More-World-API.patch
index ee3ec71b36..3fdba120da 100644
--- a/patches/server/0512-More-World-API.patch
+++ b/patches/server/0512-More-World-API.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] More World API
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 5541a9f197777124dd908e3f56050b49df821acc..de1d312d67bcdadbf7797a71909b4030e29a71f6 100644
+index e19e5f1223f8b4fae9292a936dd05b9170610134..e44a78a34aa572b10deb3f343c238cad1c521ab4 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -2145,6 +2145,28 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0536-Add-cause-to-Weather-ThunderChangeEvents.patch b/patches/server/0536-Add-cause-to-Weather-ThunderChangeEvents.patch
index 575d5d0746..9e50f7f9b7 100644
--- a/patches/server/0536-Add-cause-to-Weather-ThunderChangeEvents.patch
+++ b/patches/server/0536-Add-cause-to-Weather-ThunderChangeEvents.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Add cause to Weather/ThunderChangeEvents
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index d696ec118367f64fa7151189a4ace58287329851..355b60f8d0d600973da673db5a954345f3a0094d 100644
+index 8352e42a3dceb33477c5885433d24840e1332b87..1be314ed35d07e6db9a3f6a4f493b0f4bb59a45f 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -431,8 +431,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -95,7 +95,7 @@ index 6a3959095e57f76b3a092b32d26ff91cf1c5e068..0fa16ff37f09ecfda104b751e48bf246
if (weather.isCancelled()) {
return;
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index de1d312d67bcdadbf7797a71909b4030e29a71f6..bbc5d6834e52d2fb92fd41156ba0ae0600282b59 100644
+index e44a78a34aa572b10deb3f343c238cad1c521ab4..07986523451a755515c912e5cab5172195b929de 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -1220,7 +1220,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0549-add-per-world-spawn-limits.patch b/patches/server/0549-add-per-world-spawn-limits.patch
index 080727bf9b..ef9111f7fc 100644
--- a/patches/server/0549-add-per-world-spawn-limits.patch
+++ b/patches/server/0549-add-per-world-spawn-limits.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] add per world spawn limits
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index bbc5d6834e52d2fb92fd41156ba0ae0600282b59..6aa21f31b5d766cf6c9a94243dde652fbcb3cc61 100644
+index 07986523451a755515c912e5cab5172195b929de..9e0f2900dea70c471ea73b190b8aa2d9abd37ee6 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -226,6 +226,13 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0555-Use-getChunkIfLoadedImmediately-in-places.patch b/patches/server/0555-Use-getChunkIfLoadedImmediately-in-places.patch
index 54ff275488..a5b4d06295 100644
--- a/patches/server/0555-Use-getChunkIfLoadedImmediately-in-places.patch
+++ b/patches/server/0555-Use-getChunkIfLoadedImmediately-in-places.patch
@@ -8,7 +8,7 @@ ticket level 33 (yes getChunkIfLoaded will actually perform a chunk
load in that case).
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 355b60f8d0d600973da673db5a954345f3a0094d..83f3f05ffe61d77417bce50ce7ae6a8671605e9e 100644
+index 1be314ed35d07e6db9a3f6a4f493b0f4bb59a45f..d8f7d7512db9432f67b07e7d64a6a9349dfab245 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -234,7 +234,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0579-Add-methods-to-find-targets-for-lightning-strikes.patch b/patches/server/0579-Add-methods-to-find-targets-for-lightning-strikes.patch
index 154b2264ca..6727d31ea6 100644
--- a/patches/server/0579-Add-methods-to-find-targets-for-lightning-strikes.patch
+++ b/patches/server/0579-Add-methods-to-find-targets-for-lightning-strikes.patch
@@ -7,7 +7,7 @@ Subject: [PATCH] Add methods to find targets for lightning strikes
public net.minecraft.server.level.ServerLevel findLightningRod(Lnet/minecraft/core/BlockPos;)Ljava/util/Optional;
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 83f3f05ffe61d77417bce50ce7ae6a8671605e9e..bd99c1ca778270dc80ba83b46dbd178890dd7b53 100644
+index d8f7d7512db9432f67b07e7d64a6a9349dfab245..3137738307bb7b43190fa65da26e0b038012a9f4 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -751,6 +751,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -31,7 +31,7 @@ index 83f3f05ffe61d77417bce50ce7ae6a8671605e9e..bd99c1ca778270dc80ba83b46dbd1788
blockposition1 = blockposition1.above(2);
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 6aa21f31b5d766cf6c9a94243dde652fbcb3cc61..f4a866f7648413a9ade527efe0643b04e17baeb7 100644
+index 9e0f2900dea70c471ea73b190b8aa2d9abd37ee6..06ccf20081ecd2b56f9e5aeef988804fef10df73 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -695,6 +695,23 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0591-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch b/patches/server/0591-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch
index eefcbc16a9..8404f01faa 100644
--- a/patches/server/0591-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch
+++ b/patches/server/0591-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch
@@ -9,7 +9,7 @@ chunk through it. This should also be OK from a leak prevention/
state desync POV because the TE is getting unloaded anyways.
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index bd99c1ca778270dc80ba83b46dbd178890dd7b53..cf2b5de61eae020513c50e0903637c55b711fd1b 100644
+index 3137738307bb7b43190fa65da26e0b038012a9f4..0485312d40dca972d2477b8d4a4725ff25deacbc 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1269,9 +1269,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0593-Improve-and-expand-AsyncCatcher.patch b/patches/server/0593-Improve-and-expand-AsyncCatcher.patch
index 4fa63c4702..921cb5a829 100644
--- a/patches/server/0593-Improve-and-expand-AsyncCatcher.patch
+++ b/patches/server/0593-Improve-and-expand-AsyncCatcher.patch
@@ -42,7 +42,7 @@ index 1cb118c12e1b09cb6ae8d3b6949212b46c91b85b..21f9fc5c3111dc126d0197a02bb61541
this.effectsToProcess.add(new ProcessableEffect(mobeffect, cause));
return true;
diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
-index 1cfc906317f07a44f06a4adf021c44e34a2f1d07..6baa313b8201ed23193d7885c85606b0899ade3c 100644
+index 4eb0b0969325f39a7ae65492cccd482515a50142..5aa74c00a61282830d82359eae2b114e2a48b6d9 100644
--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
@@ -78,6 +78,7 @@ public class PersistentEntitySectionManager implements A
@@ -59,9 +59,9 @@ index 1cfc906317f07a44f06a4adf021c44e34a2f1d07..6baa313b8201ed23193d7885c85606b0
private boolean addEntity(T entity, boolean existing) {
+ org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper
// Paper start - chunk system hooks
- if (existing) {
- // I don't want to know why this is a generic type.
-@@ -146,19 +148,23 @@ public class PersistentEntitySectionManager implements A
+ // I don't want to know why this is a generic type.
+ Entity entityCasted = (Entity)entity;
+@@ -144,19 +146,23 @@ public class PersistentEntitySectionManager implements A
}
void startTicking(T entity) {
@@ -85,7 +85,7 @@ index 1cfc906317f07a44f06a4adf021c44e34a2f1d07..6baa313b8201ed23193d7885c85606b0
this.callbacks.onTrackingEnd(entity);
this.visibleEntityStorage.remove(entity);
}
-@@ -170,6 +176,7 @@ public class PersistentEntitySectionManager implements A
+@@ -168,6 +174,7 @@ public class PersistentEntitySectionManager implements A
}
public void updateChunkStatus(ChunkPos chunkPos, Visibility trackingStatus) {
@@ -93,7 +93,7 @@ index 1cfc906317f07a44f06a4adf021c44e34a2f1d07..6baa313b8201ed23193d7885c85606b0
long i = chunkPos.toLong();
if (trackingStatus == Visibility.HIDDEN) {
-@@ -214,6 +221,7 @@ public class PersistentEntitySectionManager implements A
+@@ -212,6 +219,7 @@ public class PersistentEntitySectionManager implements A
}
public void ensureChunkQueuedForLoad(long chunkPos) {
@@ -101,7 +101,7 @@ index 1cfc906317f07a44f06a4adf021c44e34a2f1d07..6baa313b8201ed23193d7885c85606b0
PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(chunkPos);
if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) {
-@@ -258,6 +266,7 @@ public class PersistentEntitySectionManager implements A
+@@ -256,6 +264,7 @@ public class PersistentEntitySectionManager implements A
}
private void requestChunkLoad(long chunkPos) {
@@ -109,7 +109,7 @@ index 1cfc906317f07a44f06a4adf021c44e34a2f1d07..6baa313b8201ed23193d7885c85606b0
this.chunkLoadStatuses.put(chunkPos, PersistentEntitySectionManager.ChunkLoadStatus.PENDING);
ChunkPos chunkcoordintpair = new ChunkPos(chunkPos);
CompletableFuture completablefuture = this.permanentStorage.loadEntities(chunkcoordintpair);
-@@ -271,6 +280,7 @@ public class PersistentEntitySectionManager implements A
+@@ -269,6 +278,7 @@ public class PersistentEntitySectionManager implements A
}
private boolean processChunkUnload(long chunkPos) {
@@ -117,7 +117,7 @@ index 1cfc906317f07a44f06a4adf021c44e34a2f1d07..6baa313b8201ed23193d7885c85606b0
boolean flag = this.storeChunkSections(chunkPos, (entityaccess) -> {
entityaccess.getPassengersAndSelf().forEach(this::unloadEntity);
}, true); // CraftBukkit - add boolean for event call
-@@ -295,6 +305,7 @@ public class PersistentEntitySectionManager implements A
+@@ -293,6 +303,7 @@ public class PersistentEntitySectionManager implements A
}
private void processPendingLoads() {
@@ -125,7 +125,7 @@ index 1cfc906317f07a44f06a4adf021c44e34a2f1d07..6baa313b8201ed23193d7885c85606b0
ChunkEntities chunkentities; // CraftBukkit - decompile error
while ((chunkentities = (ChunkEntities) this.loadingInbox.poll()) != null) {
-@@ -311,6 +322,7 @@ public class PersistentEntitySectionManager implements A
+@@ -309,6 +320,7 @@ public class PersistentEntitySectionManager implements A
}
public void tick() {
@@ -133,7 +133,7 @@ index 1cfc906317f07a44f06a4adf021c44e34a2f1d07..6baa313b8201ed23193d7885c85606b0
this.processPendingLoads();
this.processUnloads();
}
-@@ -331,6 +343,7 @@ public class PersistentEntitySectionManager implements A
+@@ -329,6 +341,7 @@ public class PersistentEntitySectionManager implements A
}
public void autoSave() {
@@ -141,7 +141,7 @@ index 1cfc906317f07a44f06a4adf021c44e34a2f1d07..6baa313b8201ed23193d7885c85606b0
this.getAllChunksToSave().forEach((java.util.function.LongConsumer) (i) -> { // CraftBukkit - decompile error
boolean flag = this.chunkVisibility.get(i) == Visibility.HIDDEN;
-@@ -345,6 +358,7 @@ public class PersistentEntitySectionManager implements A
+@@ -343,6 +356,7 @@ public class PersistentEntitySectionManager implements A
}
public void saveAll() {
@@ -149,7 +149,7 @@ index 1cfc906317f07a44f06a4adf021c44e34a2f1d07..6baa313b8201ed23193d7885c85606b0
LongSet longset = this.getAllChunksToSave();
while (!longset.isEmpty()) {
-@@ -452,6 +466,7 @@ public class PersistentEntitySectionManager implements A
+@@ -450,6 +464,7 @@ public class PersistentEntitySectionManager implements A
long i = SectionPos.asLong(blockposition);
if (i != this.currentSectionKey) {
@@ -157,7 +157,7 @@ index 1cfc906317f07a44f06a4adf021c44e34a2f1d07..6baa313b8201ed23193d7885c85606b0
Visibility visibility = this.currentSection.getStatus();
if (!this.currentSection.remove(this.entity)) {
-@@ -506,6 +521,7 @@ public class PersistentEntitySectionManager implements A
+@@ -504,6 +519,7 @@ public class PersistentEntitySectionManager implements A
@Override
public void onRemove(Entity.RemovalReason reason) {
@@ -166,7 +166,7 @@ index 1cfc906317f07a44f06a4adf021c44e34a2f1d07..6baa313b8201ed23193d7885c85606b0
PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), reason});
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index f4a866f7648413a9ade527efe0643b04e17baeb7..e221ec75905387bc0b65b816847a224b969b71fe 100644
+index 06ccf20081ecd2b56f9e5aeef988804fef10df73..f10b9898f180b231ce793cc03488df5a359b0ac0 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -1770,6 +1770,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0594-Add-paper-mobcaps-and-paper-playermobcaps.patch b/patches/server/0594-Add-paper-mobcaps-and-paper-playermobcaps.patch
index 7c4af597be..b998ef75b4 100644
--- a/patches/server/0594-Add-paper-mobcaps-and-paper-playermobcaps.patch
+++ b/patches/server/0594-Add-paper-mobcaps-and-paper-playermobcaps.patch
@@ -294,7 +294,7 @@ index 35e5a3dc58f93b85f93ec5301cc9b5c7505503bc..f69504676d2f48f3778f489ec1e08e2b
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index e221ec75905387bc0b65b816847a224b969b71fe..9811fffb16ab7b28e3a9eb5276fb18156c4ebf12 100644
+index f10b9898f180b231ce793cc03488df5a359b0ac0..5050c9a9d5369a829f5b47a56b7621de05c9a6de 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -1728,9 +1728,14 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0596-Manually-inline-methods-in-BlockPosition.patch b/patches/server/0596-Manually-inline-methods-in-BlockPosition.patch
index 90699bfbcf..89987ab3d2 100644
--- a/patches/server/0596-Manually-inline-methods-in-BlockPosition.patch
+++ b/patches/server/0596-Manually-inline-methods-in-BlockPosition.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Manually inline methods in BlockPosition
diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java
-index eea8bea0f40db8d36c59e628babf788fa920df94..0d51fb4be8b49e3b57c3c55aff6bcf13d5c78ddd 100644
+index 8e00fd1a1fb0d73e800395f4b9fffdbdb6c6b5cb..a1d54d978d34d75475f92dfb806113586e7e449c 100644
--- a/src/main/java/net/minecraft/core/BlockPos.java
+++ b/src/main/java/net/minecraft/core/BlockPos.java
@@ -576,9 +576,9 @@ public class BlockPos extends Vec3i {
diff --git a/patches/server/0605-Fix-merchant-inventory-not-closing-on-entity-removal.patch b/patches/server/0605-Fix-merchant-inventory-not-closing-on-entity-removal.patch
index 0969b0e644..4b017db5b6 100644
--- a/patches/server/0605-Fix-merchant-inventory-not-closing-on-entity-removal.patch
+++ b/patches/server/0605-Fix-merchant-inventory-not-closing-on-entity-removal.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Fix merchant inventory not closing on entity removal
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index cf2b5de61eae020513c50e0903637c55b711fd1b..0fbd077c1d3dddc97c8c99effa936470562b02a4 100644
+index 0485312d40dca972d2477b8d4a4725ff25deacbc..3ca2c0d937dc840ac64fb1efd73dbc5b045bc77d 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -2328,6 +2328,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0642-Expose-vanilla-BiomeProvider-from-WorldInfo.patch b/patches/server/0642-Expose-vanilla-BiomeProvider-from-WorldInfo.patch
index 9c88848d1d..56b9aa820a 100644
--- a/patches/server/0642-Expose-vanilla-BiomeProvider-from-WorldInfo.patch
+++ b/patches/server/0642-Expose-vanilla-BiomeProvider-from-WorldInfo.patch
@@ -18,7 +18,7 @@ index ec6f8a055131addd2ce95e76d8e781d0595dde76..7e85d9e00f41abb26fbf0e21e70ab820
biomeProvider = gen.getDefaultBiomeProvider(worldInfo);
}
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 0fbd077c1d3dddc97c8c99effa936470562b02a4..f46393f68ce7ff45302d6aef78b33b18fd10339b 100644
+index 3ca2c0d937dc840ac64fb1efd73dbc5b045bc77d..9ac6d2e87bbad00c9b97028b0e9c2a42cf29801b 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -359,7 +359,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -44,7 +44,7 @@ index f69504676d2f48f3778f489ec1e08e2b3dec85cc..d30c1e853bb2e27922a00d890dffca15
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 9811fffb16ab7b28e3a9eb5276fb18156c4ebf12..da761980a12dc08ca77dadc8773b4467a48637f9 100644
+index 5050c9a9d5369a829f5b47a56b7621de05c9a6de..0ff12d4e74c14b2b0ecf06d720c5e06edb35fcdb 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -216,6 +216,39 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0667-Fix-falling-block-spawn-methods.patch b/patches/server/0667-Fix-falling-block-spawn-methods.patch
index 631a80ae73..771588bb12 100644
--- a/patches/server/0667-Fix-falling-block-spawn-methods.patch
+++ b/patches/server/0667-Fix-falling-block-spawn-methods.patch
@@ -11,7 +11,7 @@ Restores the API behavior from previous versions of the server
public net.minecraft.world.entity.item.FallingBlockEntity (Lnet/minecraft/world/level/Level;DDDLnet/minecraft/world/level/block/state/BlockState;)V
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index da761980a12dc08ca77dadc8773b4467a48637f9..4bcd91f946dd6157a345ae8e7d049bff051911b3 100644
+index 0ff12d4e74c14b2b0ecf06d720c5e06edb35fcdb..9240460b9ec582ac01d3074c9bc923191bf0ad95 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -1405,7 +1405,12 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0695-Don-t-tick-markers.patch b/patches/server/0695-Don-t-tick-markers.patch
index b6d708b61b..c20ce60044 100644
--- a/patches/server/0695-Don-t-tick-markers.patch
+++ b/patches/server/0695-Don-t-tick-markers.patch
@@ -23,7 +23,7 @@ index 67fcba634f8183bb33834ac3b2c3dcfb8d87129e..777b789fdcdf297309cfb36fc7f77e3f
}
});
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index f46393f68ce7ff45302d6aef78b33b18fd10339b..d226b51933d25f9a31f285d7715d162302835b02 100644
+index 9ac6d2e87bbad00c9b97028b0e9c2a42cf29801b..cb33c53d441d2d535b48cb80f5ac445b52cd98be 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -2249,6 +2249,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0710-Prevent-empty-items-from-being-added-to-world.patch b/patches/server/0710-Prevent-empty-items-from-being-added-to-world.patch
index ce2a1d5359..9c65327919 100644
--- a/patches/server/0710-Prevent-empty-items-from-being-added-to-world.patch
+++ b/patches/server/0710-Prevent-empty-items-from-being-added-to-world.patch
@@ -7,7 +7,7 @@ The previous solution caused a bunch of bandaid fixes inorder to resolve edge ca
Just simply prevent them from being added to the world instead.
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index d226b51933d25f9a31f285d7715d162302835b02..100661b77466152a651d22daa7df992c28fbbecb 100644
+index cb33c53d441d2d535b48cb80f5ac445b52cd98be..f9f00a3862ab829e7695b1da484cb752bdef5c4f 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1230,6 +1230,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0716-More-Teleport-API.patch b/patches/server/0716-More-Teleport-API.patch
index 850f742352..c2d39bdc63 100644
--- a/patches/server/0716-More-Teleport-API.patch
+++ b/patches/server/0716-More-Teleport-API.patch
@@ -29,7 +29,7 @@ index ac2ad33a44ce04d9673adc08ff21a167d606e4db..349da8b85b7a122977fcad80a3996051
positionmoverotation = new PositionMoveRotation(CraftLocation.toVec3D(to), Vec3.ZERO, to.getYaw(), to.getPitch());
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
-index 4e6afa243d6108cb946a8a7cf96c4036a3c2ac0c..43786eacc72cdf3bb209d3dfb1808ea9d021a52e 100644
+index 4e6afa243d6108cb946a8a7cf96c4036a3c2ac0c..a314e401ee8a306dc12a2d98a3d400ae611a6e5a 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -222,15 +222,36 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
@@ -86,7 +86,7 @@ index 4e6afa243d6108cb946a8a7cf96c4036a3c2ac0c..43786eacc72cdf3bb209d3dfb1808ea9
+ java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>();
+
+ world.loadChunksForMoveAsync(getHandle().getBoundingBoxAt(locationClone.getX(), locationClone.getY(), locationClone.getZ()),
-+ this instanceof CraftPlayer ? ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER : ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL, (list) -> {
++ this instanceof CraftPlayer ? ca.spottedleaf.concurrentutil.util.Priority.HIGHER : ca.spottedleaf.concurrentutil.util.Priority.NORMAL, (list) -> {
+ net.minecraft.server.level.ServerChunkCache chunkProviderServer = world.getChunkSource();
+ for (net.minecraft.world.level.chunk.ChunkAccess chunk : list) {
+ chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId());
diff --git a/patches/server/0720-Warn-on-plugins-accessing-faraway-chunks.patch b/patches/server/0720-Warn-on-plugins-accessing-faraway-chunks.patch
index ebdb019a1b..c1e4213d1c 100644
--- a/patches/server/0720-Warn-on-plugins-accessing-faraway-chunks.patch
+++ b/patches/server/0720-Warn-on-plugins-accessing-faraway-chunks.patch
@@ -18,7 +18,7 @@ index 8269bf24f5e17f9e3936659aa0cbc9d4f95fb665..a6f538372830f3f80740ef503733736e
private static boolean isOutsideSpawnableHeight(int y) {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 4bcd91f946dd6157a345ae8e7d049bff051911b3..67d6840faea539b41ba3abb6d94b28e417a84511 100644
+index 9240460b9ec582ac01d3074c9bc923191bf0ad95..c0f5e4497e1ffc93f56fc2b5748bcf76569e8c3a 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -309,9 +309,24 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0723-Collision-API.patch b/patches/server/0723-Collision-API.patch
index 892f39211e..6f4b6366a8 100644
--- a/patches/server/0723-Collision-API.patch
+++ b/patches/server/0723-Collision-API.patch
@@ -22,7 +22,7 @@ index a7748f4b7c5a1630937c702b3fd5fded93793d64..a93a879117ee1eb06842242aa03f757a
// Paper end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
-index 43786eacc72cdf3bb209d3dfb1808ea9d021a52e..775675483c1f90fbe8e9e5eadab7d791e81983f5 100644
+index a314e401ee8a306dc12a2d98a3d400ae611a6e5a..01513cd747a0e18e3839e83bf5795aadb7d1e0e9 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -1194,4 +1194,20 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
diff --git a/patches/server/0735-Configurable-chat-thread-limit.patch b/patches/server/0735-Configurable-chat-thread-limit.patch
index 882f700d25..1432c06594 100644
--- a/patches/server/0735-Configurable-chat-thread-limit.patch
+++ b/patches/server/0735-Configurable-chat-thread-limit.patch
@@ -22,10 +22,10 @@ is actually processed, this is honestly really just exposed for the misnomers or
who just wanna ensure that this won't grow over a specific size if chat gets stupidly active
diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
-index b92bfd89e32becde2e7630c6116c16f8a4f6614a..73e8a524925ed6f2580d3bd01616646fabafda78 100644
+index 904d2f96a60e72aa089fdfe6be08044b04f995c1..36b96e0ed5c0d25068ec4678eddd8a19a020d345 100644
--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
-@@ -303,7 +303,18 @@ public class GlobalConfiguration extends ConfigurationPart {
+@@ -327,7 +327,18 @@ public class GlobalConfiguration extends ConfigurationPart {
@PostProcess
private void postProcess() {
diff --git a/patches/server/0740-Fix-a-bunch-of-vanilla-bugs.patch b/patches/server/0740-Fix-a-bunch-of-vanilla-bugs.patch
index 52154e4e5a..69ca61a6dc 100644
--- a/patches/server/0740-Fix-a-bunch-of-vanilla-bugs.patch
+++ b/patches/server/0740-Fix-a-bunch-of-vanilla-bugs.patch
@@ -104,7 +104,7 @@ index 75854574aa8d4aef35d84ba4c0fc7df9a67ae48c..3f3124bbb5077a18c3d3afac7748a47e
return this.anyPlayerCloseEnoughForSpawning(pos, false);
}
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 100661b77466152a651d22daa7df992c28fbbecb..79fabd52eb3a08ec9c9ab2ec5d6ff4a31a02a292 100644
+index f9f00a3862ab829e7695b1da484cb752bdef5c4f..fbee72c0b6e7d73cb0a999330ac7871bb8a7d7e4 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -764,7 +764,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0741-Remove-unnecessary-onTrackingStart-during-navigation.patch b/patches/server/0741-Remove-unnecessary-onTrackingStart-during-navigation.patch
index 9cef4dcb92..b26563ecdf 100644
--- a/patches/server/0741-Remove-unnecessary-onTrackingStart-during-navigation.patch
+++ b/patches/server/0741-Remove-unnecessary-onTrackingStart-during-navigation.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Remove unnecessary onTrackingStart during navigation warning
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 79fabd52eb3a08ec9c9ab2ec5d6ff4a31a02a292..4b2309d27867eddc50093e895503e02184e1b825 100644
+index fbee72c0b6e7d73cb0a999330ac7871bb8a7d7e4..18d39f4a8b1b15960a9b8351d46a62bea848f9c8 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -2267,7 +2267,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0761-check-global-player-list-where-appropriate.patch b/patches/server/0761-check-global-player-list-where-appropriate.patch
index 586cac4e19..e96a666062 100644
--- a/patches/server/0761-check-global-player-list-where-appropriate.patch
+++ b/patches/server/0761-check-global-player-list-where-appropriate.patch
@@ -7,7 +7,7 @@ Makes certain entities check all players when searching for a player
instead of just checking players in their world.
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 4b2309d27867eddc50093e895503e02184e1b825..3eaeeb3ec715d92fe99e14c37e224cb1e80a467b 100644
+index 18d39f4a8b1b15960a9b8351d46a62bea848f9c8..79acbaf880d5f47efd210627a60ce1e930b63e39 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -2383,4 +2383,12 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0773-Add-Sneaking-API-for-Entities.patch b/patches/server/0773-Add-Sneaking-API-for-Entities.patch
index 27bb5274f8..40141ca398 100644
--- a/patches/server/0773-Add-Sneaking-API-for-Entities.patch
+++ b/patches/server/0773-Add-Sneaking-API-for-Entities.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Add Sneaking API for Entities
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
-index 775675483c1f90fbe8e9e5eadab7d791e81983f5..5e413803f9bdc898a9a644cb123363cbdf447ad8 100644
+index 01513cd747a0e18e3839e83bf5795aadb7d1e0e9..8a715159917950091e8efd48eaca6a54488fb896 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -875,6 +875,18 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
diff --git a/patches/server/0787-Add-Entity-Body-Yaw-API.patch b/patches/server/0787-Add-Entity-Body-Yaw-API.patch
index bac79c9d69..2a926f1388 100644
--- a/patches/server/0787-Add-Entity-Body-Yaw-API.patch
+++ b/patches/server/0787-Add-Entity-Body-Yaw-API.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Add Entity Body Yaw API
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
-index 5e413803f9bdc898a9a644cb123363cbdf447ad8..f473df7a5aea6a89fb1eed28c9071b7eb3269cf8 100644
+index 8a715159917950091e8efd48eaca6a54488fb896..4209434ff066670000dadb0c59fea297f03300f4 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -1184,6 +1184,33 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
diff --git a/patches/server/0801-Prevent-GameEvents-being-fired-from-unloaded-chunks.patch b/patches/server/0801-Prevent-GameEvents-being-fired-from-unloaded-chunks.patch
index 7621693e89..bb2d2b2c31 100644
--- a/patches/server/0801-Prevent-GameEvents-being-fired-from-unloaded-chunks.patch
+++ b/patches/server/0801-Prevent-GameEvents-being-fired-from-unloaded-chunks.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Prevent GameEvents being fired from unloaded chunks
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 3eaeeb3ec715d92fe99e14c37e224cb1e80a467b..509a67aff07bcdcad47eb77e923d442349a4f20c 100644
+index 79acbaf880d5f47efd210627a60ce1e930b63e39..537a755d097d7713404d83dc47cd17828b15a906 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1385,6 +1385,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0836-Folia-scheduler-and-owned-region-API.patch b/patches/server/0836-Folia-scheduler-and-owned-region-API.patch
index ed91fc99ab..ebfcdcbcc2 100644
--- a/patches/server/0836-Folia-scheduler-and-owned-region-API.patch
+++ b/patches/server/0836-Folia-scheduler-and-owned-region-API.patch
@@ -1330,7 +1330,7 @@ index 1310bdd5ced5941b2ab4ed1f15365722c0b8252d..0193505562d7bdc6d2ab91b637a916cd
ConfigurationSerialization.registerClass(CraftOfflinePlayer.class);
ConfigurationSerialization.registerClass(CraftPlayerProfile.class);
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
-index f473df7a5aea6a89fb1eed28c9071b7eb3269cf8..d57798d6a53334eacf8235c0fd0f28d7719593fe 100644
+index 4209434ff066670000dadb0c59fea297f03300f4..df199c1cffa7795126ab83af67e184238ddb211d 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -71,6 +71,15 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
diff --git a/patches/server/0839-Only-capture-actual-tree-growth.patch b/patches/server/0839-Only-capture-actual-tree-growth.patch
index 63a72d6ca2..58fb59ab7d 100644
--- a/patches/server/0839-Only-capture-actual-tree-growth.patch
+++ b/patches/server/0839-Only-capture-actual-tree-growth.patch
@@ -17,7 +17,7 @@ index f8f570a97789ab16e57774f233506a289277d5d9..18304349c9ab24657c4152aff800dba9
}
}
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 509a67aff07bcdcad47eb77e923d442349a4f20c..1cb5e107a391ab56942cdb2d1cae7d5646a85ec6 100644
+index 537a755d097d7713404d83dc47cd17828b15a906..9270584d74edf5c1af473b1a13f7edca74cc1ec7 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -2228,6 +2228,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0846-Bandaid-fix-for-Effect.patch b/patches/server/0846-Bandaid-fix-for-Effect.patch
index 26b1a9f236..8c6534f28e 100644
--- a/patches/server/0846-Bandaid-fix-for-Effect.patch
+++ b/patches/server/0846-Bandaid-fix-for-Effect.patch
@@ -68,7 +68,7 @@ index 71733f918ed84b9879ac1b142ef6205c5e768a9c..c856384019eff2f2d0bb831ebe1ccb0f
break;
case BONE_MEAL_USE:
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 67d6840faea539b41ba3abb6d94b28e417a84511..b646f882de3ab04d54d07e9e944c261c310b58fd 100644
+index c0f5e4497e1ffc93f56fc2b5748bcf76569e8c3a..188bd33f46b6baaa3fc21c9da6fa9a9d004e899e 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -1382,7 +1382,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0848-API-for-an-entity-s-scoreboard-name.patch b/patches/server/0848-API-for-an-entity-s-scoreboard-name.patch
index ac005db821..68f3c7789a 100644
--- a/patches/server/0848-API-for-an-entity-s-scoreboard-name.patch
+++ b/patches/server/0848-API-for-an-entity-s-scoreboard-name.patch
@@ -7,7 +7,7 @@ Was obtainable through different methods, but you had to use different
methods depending on the implementation of Entity you were working with.
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
-index d57798d6a53334eacf8235c0fd0f28d7719593fe..0d2d1b6eab153aea6015bd69f2bad58e67d065c3 100644
+index df199c1cffa7795126ab83af67e184238ddb211d..dd84f7d6874a799aa09349fe0a1bd6ebb4cea2dd 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -1264,4 +1264,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
diff --git a/patches/server/0858-Expand-Pose-API.patch b/patches/server/0858-Expand-Pose-API.patch
index 891dd712d6..5fcd53b27b 100644
--- a/patches/server/0858-Expand-Pose-API.patch
+++ b/patches/server/0858-Expand-Pose-API.patch
@@ -25,7 +25,7 @@ index d8331b2d4ad3ebebb6ecbbf083f3464dad38f623..63e68376d1854f4f7ff1a1d0a11fcec1
if (pose == this.getPose()) {
return;
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
-index 0d2d1b6eab153aea6015bd69f2bad58e67d065c3..9c8e2731ca37a16cb538da27190c085bbc9791e3 100644
+index dd84f7d6874a799aa09349fe0a1bd6ebb4cea2dd..11a08ff211a9a32a825519ddb1736e02cf7f685e 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -900,6 +900,20 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
diff --git a/patches/server/0881-Fix-missing-map-initialize-event-call.patch b/patches/server/0881-Fix-missing-map-initialize-event-call.patch
index 795f551f59..f2047845c6 100644
--- a/patches/server/0881-Fix-missing-map-initialize-event-call.patch
+++ b/patches/server/0881-Fix-missing-map-initialize-event-call.patch
@@ -7,7 +7,7 @@ Subject: [PATCH] Fix missing map initialize event call
public net.minecraft.world.level.storage.DimensionDataStorage readSavedData(Ljava/util/function/Function;Lnet/minecraft/util/datafix/DataFixTypes;Ljava/lang/String;)Lnet/minecraft/world/level/saveddata/SavedData;
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 1cb5e107a391ab56942cdb2d1cae7d5646a85ec6..7acb24c2a34fdcbcb1c0e3cc03b01996689667d3 100644
+index 9270584d74edf5c1af473b1a13f7edca74cc1ec7..1bacbb0d0bd5198d0f946a959b2335d6fba0ca88 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1726,13 +1726,29 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0890-Add-predicate-for-blocks-when-raytracing.patch b/patches/server/0890-Add-predicate-for-blocks-when-raytracing.patch
index ea7280cbe5..fdeae8627c 100644
--- a/patches/server/0890-Add-predicate-for-blocks-when-raytracing.patch
+++ b/patches/server/0890-Add-predicate-for-blocks-when-raytracing.patch
@@ -47,7 +47,7 @@ index 7e1a332168357b9af14dbe3299549c2c93903fa6..93738c7dea1ea3d19013a47380391274
Vec3 vec3d = raytrace1.getFrom().subtract(raytrace1.getTo());
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index b646f882de3ab04d54d07e9e944c261c310b58fd..69da1555f68e7603fd355307c3458688b92bc37f 100644
+index 188bd33f46b6baaa3fc21c9da6fa9a9d004e899e..041a6042a91f2a8933d7f9bcb44bb78894ffd405 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -1116,9 +1116,15 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0893-Fix-strikeLightningEffect-powers-lightning-rods-and-.patch b/patches/server/0893-Fix-strikeLightningEffect-powers-lightning-rods-and-.patch
index ba091fe841..ac9852081f 100644
--- a/patches/server/0893-Fix-strikeLightningEffect-powers-lightning-rods-and-.patch
+++ b/patches/server/0893-Fix-strikeLightningEffect-powers-lightning-rods-and-.patch
@@ -45,7 +45,7 @@ index 152ecd38814089333b8d61538297ce720756d2c3..12127b14babf646711d3a118416453c4
if (world instanceof ServerLevel) {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 69da1555f68e7603fd355307c3458688b92bc37f..6d1d387598a15c5b79bca4f496eb9375e9c9376d 100644
+index 041a6042a91f2a8933d7f9bcb44bb78894ffd405..b1ad6c47d3d42c93411753d4505ac9142b1697d3 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -742,7 +742,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0897-Fix-missing-event-call-for-entity-teleport-API.patch b/patches/server/0897-Fix-missing-event-call-for-entity-teleport-API.patch
index 90e9f0e671..c00c1e49bd 100644
--- a/patches/server/0897-Fix-missing-event-call-for-entity-teleport-API.patch
+++ b/patches/server/0897-Fix-missing-event-call-for-entity-teleport-API.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Fix missing event call for entity teleport API
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
-index 9c8e2731ca37a16cb538da27190c085bbc9791e3..c1d3dd2bd217efd6914bceb1027fa12b06c22a55 100644
+index 11a08ff211a9a32a825519ddb1736e02cf7f685e..7536ab5c22d97a074c08a95fff6bc756d61e387d 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -259,6 +259,17 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
diff --git a/patches/server/0899-Don-t-fire-sync-events-during-worldgen.patch b/patches/server/0899-Don-t-fire-sync-events-during-worldgen.patch
index d3a68412bc..7504923127 100644
--- a/patches/server/0899-Don-t-fire-sync-events-during-worldgen.patch
+++ b/patches/server/0899-Don-t-fire-sync-events-during-worldgen.patch
@@ -19,7 +19,7 @@ where generation happened directly to a ServerLevel and the
entity still has the flag set.
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 7acb24c2a34fdcbcb1c0e3cc03b01996689667d3..7a713fd31a2155b1c77c54817a67e354b1d4640d 100644
+index 1bacbb0d0bd5198d0f946a959b2335d6fba0ca88..a628da8a0ed7ae2c7b46df3881bd75dc5b4fd607 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1219,6 +1219,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0900-Add-Structure-check-API.patch b/patches/server/0900-Add-Structure-check-API.patch
index 5bbc01bd9f..d138a01f44 100644
--- a/patches/server/0900-Add-Structure-check-API.patch
+++ b/patches/server/0900-Add-Structure-check-API.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Add Structure check API
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 6d1d387598a15c5b79bca4f496eb9375e9c9376d..50b3f5c185b8bb7965226c97a37faf714f5beb0a 100644
+index b1ad6c47d3d42c93411753d4505ac9142b1697d3..34e8afae13085f0a9ce0c916d911c88c395418e0 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -250,6 +250,15 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0925-Add-BlockBreakProgressUpdateEvent.patch b/patches/server/0925-Add-BlockBreakProgressUpdateEvent.patch
index 81399fe283..2c5061f42f 100644
--- a/patches/server/0925-Add-BlockBreakProgressUpdateEvent.patch
+++ b/patches/server/0925-Add-BlockBreakProgressUpdateEvent.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Add BlockBreakProgressUpdateEvent
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 7a713fd31a2155b1c77c54817a67e354b1d4640d..942eb8c1ef575de53b1591b39014a39edb054a1f 100644
+index a628da8a0ed7ae2c7b46df3881bd75dc5b4fd607..99e6021bc0dc5775f4443bdb77debd535a2cf29f 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1315,6 +1315,17 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0928-More-Raid-API.patch b/patches/server/0928-More-Raid-API.patch
index daaeb2bdea..2039ffa00d 100644
--- a/patches/server/0928-More-Raid-API.patch
+++ b/patches/server/0928-More-Raid-API.patch
@@ -86,7 +86,7 @@ index b8ce1c1c2447f9cff1717bfcfd6eb911ade0d4b3..51f21af9d75769abdcba713b9aa33392
+ // Paper end - more Raid API
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 50b3f5c185b8bb7965226c97a37faf714f5beb0a..51b10d9cceb4dfbea5e1d82d0404948c6cdc5944 100644
+index 34e8afae13085f0a9ce0c916d911c88c395418e0..406a3b4a6e213879a9b262d2ffa9ba404cf31cc1 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -2326,6 +2326,14 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0929-Add-onboarding-message-for-initial-server-start.patch b/patches/server/0929-Add-onboarding-message-for-initial-server-start.patch
index df74ef8161..d5378f34f1 100644
--- a/patches/server/0929-Add-onboarding-message-for-initial-server-start.patch
+++ b/patches/server/0929-Add-onboarding-message-for-initial-server-start.patch
@@ -17,7 +17,7 @@ index d9502ba028a96f9cc846f9ed428bd8066b857ca3..87e5f614ba988547a827486740db217e
node = loader.load();
this.verifyGlobalConfigVersion(node);
diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
-index 73e8a524925ed6f2580d3bd01616646fabafda78..450a1cc8f1624dce2daf52210d017e0732b1bdf7 100644
+index 36b96e0ed5c0d25068ec4678eddd8a19a020d345..8a0cb603cd4dbfa1839e0f4e1606876cbb373277 100644
--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
@@ -27,6 +27,7 @@ public class GlobalConfiguration extends ConfigurationPart {
diff --git a/patches/server/0930-Configurable-max-block-fluid-ticks.patch b/patches/server/0930-Configurable-max-block-fluid-ticks.patch
index 4699e81a98..15cd7fb486 100644
--- a/patches/server/0930-Configurable-max-block-fluid-ticks.patch
+++ b/patches/server/0930-Configurable-max-block-fluid-ticks.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Configurable max block/fluid ticks
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 942eb8c1ef575de53b1591b39014a39edb054a1f..46a678ad3487d92e4c63731457bb4a421c2f07c2 100644
+index 99e6021bc0dc5775f4443bdb77debd535a2cf29f..eed2d1ec425bbd34563fae9e69a4518ec154fc23 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -492,9 +492,9 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0972-disable-forced-empty-world-ticks.patch b/patches/server/0972-disable-forced-empty-world-ticks.patch
index cf5993dc22..343cd32ea2 100644
--- a/patches/server/0972-disable-forced-empty-world-ticks.patch
+++ b/patches/server/0972-disable-forced-empty-world-ticks.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] disable forced empty world ticks
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 46a678ad3487d92e4c63731457bb4a421c2f07c2..86d6a01f94b5140932eb86b9523c6e3e43864a09 100644
+index eed2d1ec425bbd34563fae9e69a4518ec154fc23..982d44b539e189f4a857e72554cc81f8a4501ad6 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -519,7 +519,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0974-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch b/patches/server/0974-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
index 009447d681..f4600638da 100644
--- a/patches/server/0974-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
+++ b/patches/server/0974-Improve-Maps-in-item-frames-performance-and-bug-fixe.patch
@@ -13,7 +13,7 @@ custom renderers are in use, defaulting to the much simpler Vanilla system.
Additionally, numerous issues to player position tracking on maps has been fixed.
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 86d6a01f94b5140932eb86b9523c6e3e43864a09..bbfe17233d23bbb787656ebedc92fdd2de56daed 100644
+index 982d44b539e189f4a857e72554cc81f8a4501ad6..fe610561e6fbb9bc547d27123793395fb0ad80aa 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -2363,6 +2363,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
diff --git a/patches/server/0978-Entity-Activation-Range-2.0.patch b/patches/server/0978-Entity-Activation-Range-2.0.patch
index f340e669a8..65d38450a6 100644
--- a/patches/server/0978-Entity-Activation-Range-2.0.patch
+++ b/patches/server/0978-Entity-Activation-Range-2.0.patch
@@ -17,7 +17,7 @@ Adds villagers as separate config
public net.minecraft.world.entity.Entity isInsidePortal
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index bbfe17233d23bbb787656ebedc92fdd2de56daed..3fb8370abd10bb702b969c43490c79352b0242df 100644
+index fe610561e6fbb9bc547d27123793395fb0ad80aa..ce5fd95cabadd7c92726c401ae35e05dde3e30f6 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -2,7 +2,6 @@ package net.minecraft.server.level;
diff --git a/patches/server/0979-Anti-Xray.patch b/patches/server/0979-Anti-Xray.patch
index 3a140d851c..dba0bf0bbc 100644
--- a/patches/server/0979-Anti-Xray.patch
+++ b/patches/server/0979-Anti-Xray.patch
@@ -1104,7 +1104,7 @@ index 183b2191fa1c1b27adedf39593e1b5a223fb1279..8ead66c134688b11dca15f6509147e72
private ClientboundLevelChunkWithLightPacket(RegistryFriendlyByteBuf buf) {
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 3fb8370abd10bb702b969c43490c79352b0242df..c17824fa01c4682c2b97bb60bfa401a1a2ef9c12 100644
+index ce5fd95cabadd7c92726c401ae35e05dde3e30f6..878bd04b63f257cc625953e45b953beb06917107 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -344,7 +344,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
@@ -1604,7 +1604,7 @@ index 52710b4196dde7118eb1438a2f78b7c9557e00f8..8348ab336029848eab1cbe9b67b056ab
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 51b10d9cceb4dfbea5e1d82d0404948c6cdc5944..d3c845a997ad464cc25676443f567e6ada9ab360 100644
+index 406a3b4a6e213879a9b262d2ffa9ba404cf31cc1..367e662c2ca370f569fc65e3f74ec322140630db 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -472,11 +472,16 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/0985-Optimize-Bit-Operations-by-inlining.patch b/patches/server/0985-Optimize-Bit-Operations-by-inlining.patch
index 02b75062fb..2c71fd1153 100644
--- a/patches/server/0985-Optimize-Bit-Operations-by-inlining.patch
+++ b/patches/server/0985-Optimize-Bit-Operations-by-inlining.patch
@@ -7,7 +7,7 @@ Inline bit operations and reduce instruction count to make these hot
operations faster
diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java
-index 0d51fb4be8b49e3b57c3c55aff6bcf13d5c78ddd..a5dab9b1652ac76372d88316e2c165eed6317891 100644
+index a1d54d978d34d75475f92dfb806113586e7e449c..f58a94efafbc01d402cd03a108bb90f60930a316 100644
--- a/src/main/java/net/minecraft/core/BlockPos.java
+++ b/src/main/java/net/minecraft/core/BlockPos.java
@@ -50,15 +50,17 @@ public class BlockPos extends Vec3i {
diff --git a/patches/server/0992-Properly-resend-entities.patch b/patches/server/0992-Properly-resend-entities.patch
index 6acf6d708b..dbadaf99bb 100644
--- a/patches/server/0992-Properly-resend-entities.patch
+++ b/patches/server/0992-Properly-resend-entities.patch
@@ -236,7 +236,7 @@ index 04760d8ba7c560bd9d11191c666715ae8c3e4bff..768f90682cd10045c16337fecc2702f5
// CraftBukkit end
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
-index c1d3dd2bd217efd6914bceb1027fa12b06c22a55..ca95a36b0149d4b8a67c3b42316c5d9d0415f5dd 100644
+index 7536ab5c22d97a074c08a95fff6bc756d61e387d..b0e49ad831f1ebc6b126bf82c5fddaebffb91312 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -1013,7 +1013,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
diff --git a/patches/server/1020-Add-FeatureFlag-API.patch b/patches/server/1020-Add-FeatureFlag-API.patch
index dc614e582a..cc3530746f 100644
--- a/patches/server/1020-Add-FeatureFlag-API.patch
+++ b/patches/server/1020-Add-FeatureFlag-API.patch
@@ -162,7 +162,7 @@ index f0bd7d01f56bb792886354ca4f199e46c2cf7503..adc6741e0e017660fbd39a62b69be1e6
+ // Paper end - feature flag API
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index d3c845a997ad464cc25676443f567e6ada9ab360..125a427bd23df17773f50ecf8babc72f9502cba7 100644
+index 367e662c2ca370f569fc65e3f74ec322140630db..7d360620bd78f28f366815a019c57e5058d9f2a3 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -2379,10 +2379,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/1028-Improve-entity-effect-API.patch b/patches/server/1028-Improve-entity-effect-API.patch
index cc5195f74b..6599dd255e 100644
--- a/patches/server/1028-Improve-entity-effect-API.patch
+++ b/patches/server/1028-Improve-entity-effect-API.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Improve entity effect API
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
-index ca95a36b0149d4b8a67c3b42316c5d9d0415f5dd..64c6f54cc4d0c22bc972b808cb92925cc7526db2 100644
+index b0e49ad831f1ebc6b126bf82c5fddaebffb91312..179886dcbda29c5cdb7dbd43e44951ae38d9df96 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -1300,4 +1300,15 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
diff --git a/patches/server/1032-Void-damage-configuration-API.patch b/patches/server/1032-Void-damage-configuration-API.patch
index b71b4b8314..afa0337fd7 100644
--- a/patches/server/1032-Void-damage-configuration-API.patch
+++ b/patches/server/1032-Void-damage-configuration-API.patch
@@ -33,7 +33,7 @@ index 6db0613904c172fb6ae7e868f542f56aeaa63a5e..22b3d3d945cbddae25abfca7d900324c
protected void updateSwingTime() {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 125a427bd23df17773f50ecf8babc72f9502cba7..b2eb8cf1de3b4b81587531bb4c0d4b512f5d5d5d 100644
+index 7d360620bd78f28f366815a019c57e5058d9f2a3..6dc3fc701d1e16a51d99f934ea3dc192363a6762 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -167,6 +167,41 @@ public class CraftWorld extends CraftRegionAccessor implements World {
diff --git a/patches/server/1040-Rewrite-dataconverter-system.patch b/patches/server/1037-Rewrite-dataconverter-system.patch
similarity index 100%
rename from patches/server/1040-Rewrite-dataconverter-system.patch
rename to patches/server/1037-Rewrite-dataconverter-system.patch
diff --git a/patches/server/1037-fixup-ConcurrentUtil.patch b/patches/server/1037-fixup-ConcurrentUtil.patch
deleted file mode 100644
index 60d57a1656..0000000000
--- a/patches/server/1037-fixup-ConcurrentUtil.patch
+++ /dev/null
@@ -1,6177 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf
-Date: Mon, 21 Oct 2024 11:47:34 -0700
-Subject: [PATCH] fixup! ConcurrentUtil
-
-
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java
-deleted file mode 100644
-index 094eff418b4e3bffce020d650931b4d9e58fa9ed..0000000000000000000000000000000000000000
---- a/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java
-+++ /dev/null
-@@ -1,149 +0,0 @@
--package ca.spottedleaf.concurrentutil.collection;
--
--import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
--import ca.spottedleaf.concurrentutil.util.Validate;
--
--import java.lang.invoke.VarHandle;
--import java.util.ConcurrentModificationException;
--
--/**
-- * Single reader thread single writer thread queue. The reader side of the queue is ordered by acquire semantics,
-- * and the writer side of the queue is ordered by release semantics.
-- */
--// TODO test
--public class SRSWLinkedQueue {
--
-- // always non-null
-- protected LinkedNode head;
--
-- // always non-null
-- protected LinkedNode tail;
--
-- /* IMPL NOTE: Leave hashCode and equals to their defaults */
--
-- public SRSWLinkedQueue() {
-- final LinkedNode dummy = new LinkedNode<>(null, null);
-- this.head = this.tail = dummy;
-- }
--
-- /**
-- * Must be the reader thread.
-- *
-- *
-- * Returns, without removing, the first element of this queue.
-- *
-- * @return Returns, without removing, the first element of this queue.
-- */
-- public E peekFirst() {
-- LinkedNode head = this.head;
-- E ret = head.getElementPlain();
-- if (ret == null) {
-- head = head.getNextAcquire();
-- if (head == null) {
-- // empty
-- return null;
-- }
-- // update head reference for next poll() call
-- this.head = head;
-- // guaranteed to be non-null
-- ret = head.getElementPlain();
-- if (ret == null) {
-- throw new ConcurrentModificationException("Multiple reader threads");
-- }
-- }
--
-- return ret;
-- }
--
-- /**
-- * Must be the reader thread.
-- *
-- *
-- * Returns and removes the first element of this queue.
-- *
-- * @return Returns and removes the first element of this queue.
-- */
-- public E poll() {
-- LinkedNode head = this.head;
-- E ret = head.getElementPlain();
-- if (ret == null) {
-- head = head.getNextAcquire();
-- if (head == null) {
-- // empty
-- return null;
-- }
-- // guaranteed to be non-null
-- ret = head.getElementPlain();
-- if (ret == null) {
-- throw new ConcurrentModificationException("Multiple reader threads");
-- }
-- }
--
-- head.setElementPlain(null);
-- LinkedNode next = head.getNextAcquire();
-- this.head = next == null ? head : next;
--
-- return ret;
-- }
--
-- /**
-- * Must be the writer thread.
-- *
-- *
-- * Adds the element to the end of the queue.
-- *
-- *
-- * @throws NullPointerException If the provided element is null
-- */
-- public void addLast(final E element) {
-- Validate.notNull(element, "Provided element cannot be null");
-- final LinkedNode append = new LinkedNode<>(element, null);
--
-- this.tail.setNextRelease(append);
-- this.tail = append;
-- }
--
-- protected static final class LinkedNode {
--
-- protected volatile Object element;
-- protected volatile LinkedNode next;
--
-- protected static final VarHandle ELEMENT_HANDLE = ConcurrentUtil.getVarHandle(LinkedNode.class, "element", Object.class);
-- protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(LinkedNode.class, "next", LinkedNode.class);
--
-- protected LinkedNode(final Object element, final LinkedNode next) {
-- ELEMENT_HANDLE.set(this, element);
-- NEXT_HANDLE.set(this, next);
-- }
--
-- /* element */
--
-- @SuppressWarnings("unchecked")
-- protected final E getElementPlain() {
-- return (E)ELEMENT_HANDLE.get(this);
-- }
--
-- protected final void setElementPlain(final E update) {
-- ELEMENT_HANDLE.set(this, (Object)update);
-- }
-- /* next */
--
-- @SuppressWarnings("unchecked")
-- protected final LinkedNode getNextPlain() {
-- return (LinkedNode)NEXT_HANDLE.get(this);
-- }
--
-- @SuppressWarnings("unchecked")
-- protected final LinkedNode getNextAcquire() {
-- return (LinkedNode)NEXT_HANDLE.getAcquire(this);
-- }
--
-- protected final void setNextPlain(final LinkedNode next) {
-- NEXT_HANDLE.set(this, next);
-- }
--
-- protected final void setNextRelease(final LinkedNode next) {
-- NEXT_HANDLE.setRelease(this, next);
-- }
-- }
--}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..6bad6f8ecc0944d2f406924c7de7e227ff1e70fa
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java
-@@ -0,0 +1,110 @@
-+package ca.spottedleaf.concurrentutil.completable;
-+
-+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
-+import ca.spottedleaf.concurrentutil.executor.Cancellable;
-+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
-+import org.slf4j.Logger;
-+import org.slf4j.LoggerFactory;
-+import java.util.function.BiConsumer;
-+
-+public final class CallbackCompletable {
-+
-+ private static final Logger LOGGER = LoggerFactory.getLogger(CallbackCompletable.class);
-+
-+ private final MultiThreadedQueue> waiters = new MultiThreadedQueue<>();
-+ private T result;
-+ private Throwable throwable;
-+ private volatile boolean completed;
-+
-+ public boolean isCompleted() {
-+ return this.completed;
-+ }
-+
-+ /**
-+ * Note: Can only use after calling {@link #addAsynchronousWaiter(BiConsumer)}, as this function performs zero
-+ * synchronisation
-+ */
-+ public T getResult() {
-+ return this.result;
-+ }
-+
-+ /**
-+ * Note: Can only use after calling {@link #addAsynchronousWaiter(BiConsumer)}, as this function performs zero
-+ * synchronisation
-+ */
-+ public Throwable getThrowable() {
-+ return this.throwable;
-+ }
-+
-+ /**
-+ * Adds a waiter that should only be completed asynchronously by the complete() calls. If complete()
-+ * has already been called, returns {@code null} and does not invoke the specified consumer.
-+ * @param consumer Consumer to be executed on completion
-+ * @throws NullPointerException If consumer is null
-+ * @return A cancellable which will control the execution of the specified consumer
-+ */
-+ public Cancellable addAsynchronousWaiter(final BiConsumer consumer) {
-+ if (this.waiters.add(consumer)) {
-+ return new CancellableImpl(consumer);
-+ }
-+ return null;
-+ }
-+
-+ private void completeAllWaiters(final T result, final Throwable throwable) {
-+ this.completed = true;
-+ BiConsumer waiter;
-+ while ((waiter = this.waiters.pollOrBlockAdds()) != null) {
-+ this.completeWaiter(waiter, result, throwable);
-+ }
-+ }
-+
-+ private void completeWaiter(final BiConsumer consumer, final T result, final Throwable throwable) {
-+ try {
-+ consumer.accept(result, throwable);
-+ } catch (final Throwable throwable2) {
-+ LOGGER.error("Failed to complete callback " + ConcurrentUtil.genericToString(consumer), throwable2);
-+ }
-+ }
-+
-+ /**
-+ * Adds a waiter that will be completed asynchronously by the complete() calls. If complete()
-+ * has already been called, then invokes the consumer synchronously with the completed result.
-+ * @param consumer Consumer to be executed on completion
-+ * @throws NullPointerException If consumer is null
-+ * @return A cancellable which will control the execution of the specified consumer
-+ */
-+ public Cancellable addWaiter(final BiConsumer consumer) {
-+ if (this.waiters.add(consumer)) {
-+ return new CancellableImpl(consumer);
-+ }
-+ this.completeWaiter(consumer, this.result, this.throwable);
-+ return new CancellableImpl(consumer);
-+ }
-+
-+ public void complete(final T result) {
-+ this.result = result;
-+ this.completeAllWaiters(result, null);
-+ }
-+
-+ public void completeWithThrowable(final Throwable throwable) {
-+ if (throwable == null) {
-+ throw new NullPointerException("Throwable cannot be null");
-+ }
-+ this.throwable = throwable;
-+ this.completeAllWaiters(null, throwable);
-+ }
-+
-+ private final class CancellableImpl implements Cancellable {
-+
-+ private final BiConsumer waiter;
-+
-+ private CancellableImpl(final BiConsumer waiter) {
-+ this.waiter = waiter;
-+ }
-+
-+ @Override
-+ public boolean cancel() {
-+ return CallbackCompletable.this.waiters.remove(this.waiter);
-+ }
-+ }
-+}
-\ No newline at end of file
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java
-index 46d1bd01542ebeeffc0006a5c585a50dbbbff907..365616439fa079017d648ed7f6ddf6950a691adf 100644
---- a/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java
-@@ -1,112 +1,737 @@
- package ca.spottedleaf.concurrentutil.completable;
-
--import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
--import ca.spottedleaf.concurrentutil.executor.Cancellable;
- import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
-+import ca.spottedleaf.concurrentutil.util.Validate;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
-+import java.lang.invoke.VarHandle;
-+import java.util.concurrent.CompletableFuture;
-+import java.util.concurrent.CompletionException;
-+import java.util.concurrent.CompletionStage;
-+import java.util.concurrent.Executor;
-+import java.util.concurrent.ForkJoinPool;
-+import java.util.concurrent.locks.LockSupport;
- import java.util.function.BiConsumer;
-+import java.util.function.BiFunction;
-+import java.util.function.Consumer;
-+import java.util.function.Function;
-+import java.util.function.Supplier;
-
- public final class Completable {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(Completable.class);
-+ private static final Function super Throwable, ? extends Throwable> DEFAULT_EXCEPTION_HANDLER = (final Throwable thr) -> {
-+ LOGGER.error("Unhandled exception during Completable operation", thr);
-+ return thr;
-+ };
-
-- private final MultiThreadedQueue> waiters = new MultiThreadedQueue<>();
-- private T result;
-- private Throwable throwable;
-- private volatile boolean completed;
-+ public static Executor getDefaultExecutor() {
-+ return ForkJoinPool.commonPool();
-+ }
-+
-+ private static final Transform, ?> COMPLETED_STACK = new Transform<>(null, null, null, null) {
-+ @Override
-+ public void run() {}
-+ };
-+ private volatile Transform, T> completeStack;
-+ private static final VarHandle COMPLETE_STACK_HANDLE = ConcurrentUtil.getVarHandle(Completable.class, "completeStack", Transform.class);
-+
-+ private static final Object NULL_MASK = new Object();
-+ private volatile Object result;
-+ private static final VarHandle RESULT_HANDLE = ConcurrentUtil.getVarHandle(Completable.class, "result", Object.class);
-
-- public boolean isCompleted() {
-- return this.completed;
-+ private Object getResultPlain() {
-+ return (Object)RESULT_HANDLE.get(this);
- }
-
-- /**
-- * Note: Can only use after calling {@link #addAsynchronousWaiter(BiConsumer)}, as this function performs zero
-- * synchronisation
-- */
-- public T getResult() {
-- return this.result;
-+ private Object getResultVolatile() {
-+ return (Object)RESULT_HANDLE.getVolatile(this);
- }
-
-- /**
-- * Note: Can only use after calling {@link #addAsynchronousWaiter(BiConsumer)}, as this function performs zero
-- * synchronisation
-- */
-- public Throwable getThrowable() {
-- return this.throwable;
-+ private void pushStackOrRun(final Transform, T> push) {
-+ int failures = 0;
-+ for (Transform, T> curr = (Transform, T>)COMPLETE_STACK_HANDLE.getVolatile(this);;) {
-+ if (curr == COMPLETED_STACK) {
-+ push.execute();
-+ return;
-+ }
-+
-+ push.next = curr;
-+
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = (Transform, T>)COMPLETE_STACK_HANDLE.compareAndExchange(this, curr, push))) {
-+ return;
-+ }
-+ push.next = null;
-+ ++failures;
-+ }
- }
-
-- /**
-- * Adds a waiter that should only be completed asynchronously by the complete() calls. If complete()
-- * has already been called, returns {@code null} and does not invoke the specified consumer.
-- * @param consumer Consumer to be executed on completion
-- * @throws NullPointerException If consumer is null
-- * @return A cancellable which will control the execution of the specified consumer
-- */
-- public Cancellable addAsynchronousWaiter(final BiConsumer consumer) {
-- if (this.waiters.add(consumer)) {
-- return new CancellableImpl(consumer);
-+ private void propagateStack() {
-+ Transform, T> topStack = (Transform, T>)COMPLETE_STACK_HANDLE.getAndSet(this, COMPLETED_STACK);
-+ while (topStack != null) {
-+ topStack.execute();
-+ topStack = topStack.next;
- }
-- return null;
- }
-
-- private void completeAllWaiters(final T result, final Throwable throwable) {
-- this.completed = true;
-- BiConsumer waiter;
-- while ((waiter = this.waiters.pollOrBlockAdds()) != null) {
-- this.completeWaiter(waiter, result, throwable);
-+ private static Object maskNull(final Object res) {
-+ return res == null ? NULL_MASK : res;
-+ }
-+
-+ private static Object unmaskNull(final Object res) {
-+ return res == NULL_MASK ? null : res;
-+ }
-+
-+ private static Executor checkExecutor(final Executor executor) {
-+ return Validate.notNull(executor, "Executor may not be null");
-+ }
-+
-+ public Completable() {}
-+
-+ private Completable(final Object complete) {
-+ COMPLETE_STACK_HANDLE.set(this, COMPLETED_STACK);
-+ RESULT_HANDLE.setRelease(this, complete);
-+ }
-+
-+ public static Completable completed(final T value) {
-+ return new Completable<>(maskNull(value));
-+ }
-+
-+ public static Completable failed(final Throwable ex) {
-+ Validate.notNull(ex, "Exception may not be null");
-+
-+ return new Completable<>(new ExceptionResult(ex));
-+ }
-+
-+ public static Completable supplied(final Supplier supplier) {
-+ return supplied(supplier, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public static Completable supplied(final Supplier supplier, final Function super Throwable, ? extends Throwable> exceptionHandler) {
-+ try {
-+ return completed(supplier.get());
-+ } catch (final Throwable throwable) {
-+ Throwable complete;
-+ try {
-+ complete = exceptionHandler.apply(throwable);
-+ } catch (final Throwable thr2) {
-+ throwable.addSuppressed(thr2);
-+ complete = throwable;
-+ }
-+ return failed(complete);
- }
- }
-
-- private void completeWaiter(final BiConsumer consumer, final T result, final Throwable throwable) {
-+ public static Completable suppliedAsync(final Supplier supplier, final Executor executor) {
-+ return suppliedAsync(supplier, executor, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public static Completable suppliedAsync(final Supplier supplier, final Executor executor, final Function super Throwable, ? extends Throwable> exceptionHandler) {
-+ final Completable ret = new Completable<>();
-+
-+ class AsyncSuppliedCompletable implements Runnable, CompletableFuture.AsynchronousCompletionTask {
-+ @Override
-+ public void run() {
-+ try {
-+ ret.complete(supplier.get());
-+ } catch (final Throwable throwable) {
-+ Throwable complete;
-+ try {
-+ complete = exceptionHandler.apply(throwable);
-+ } catch (final Throwable thr2) {
-+ throwable.addSuppressed(thr2);
-+ complete = throwable;
-+ }
-+ ret.completeExceptionally(complete);
-+ }
-+ }
-+ }
-+
- try {
-- consumer.accept(result, throwable);
-- } catch (final ThreadDeath death) {
-- throw death;
-- } catch (final Throwable throwable2) {
-- LOGGER.error("Failed to complete callback " + ConcurrentUtil.genericToString(consumer), throwable2);
-+ executor.execute(new AsyncSuppliedCompletable());
-+ } catch (final Throwable throwable) {
-+ Throwable complete;
-+ try {
-+ complete = exceptionHandler.apply(throwable);
-+ } catch (final Throwable thr2) {
-+ throwable.addSuppressed(thr2);
-+ complete = throwable;
-+ }
-+ ret.completeExceptionally(complete);
-+ }
-+
-+ return ret;
-+ }
-+
-+ private boolean completeRaw(final Object value) {
-+ if ((Object)RESULT_HANDLE.getVolatile(this) != null || !(boolean)RESULT_HANDLE.compareAndSet(this, (Object)null, value)) {
-+ return false;
-+ }
-+
-+ this.propagateStack();
-+ return true;
-+ }
-+
-+ public boolean complete(final T result) {
-+ return this.completeRaw(maskNull(result));
-+ }
-+
-+ public boolean completeExceptionally(final Throwable exception) {
-+ Validate.notNull(exception, "Exception may not be null");
-+
-+ return this.completeRaw(new ExceptionResult(exception));
-+ }
-+
-+ public boolean isDone() {
-+ return this.getResultVolatile() != null;
-+ }
-+
-+ public boolean isNormallyComplete() {
-+ return this.getResultVolatile() != null && !(this.getResultVolatile() instanceof ExceptionResult);
-+ }
-+
-+ public boolean isExceptionallyComplete() {
-+ return this.getResultVolatile() instanceof ExceptionResult;
-+ }
-+
-+ public Throwable getException() {
-+ final Object res = this.getResultVolatile();
-+ if (res == null) {
-+ return null;
-+ }
-+
-+ if (!(res instanceof ExceptionResult exRes)) {
-+ throw new IllegalStateException("Not completed exceptionally");
-+ }
-+
-+ return exRes.ex;
-+ }
-+
-+ public T getNow(final T dfl) throws CompletionException {
-+ final Object res = this.getResultVolatile();
-+ if (res == null) {
-+ return dfl;
-+ }
-+
-+ if (res instanceof ExceptionResult exRes) {
-+ throw new CompletionException(exRes.ex);
-+ }
-+
-+ return (T)unmaskNull(res);
-+ }
-+
-+ public T join() throws CompletionException {
-+ if (this.isDone()) {
-+ return this.getNow(null);
-+ }
-+
-+ final UnparkTransform unparkTransform = new UnparkTransform<>(this, Thread.currentThread());
-+
-+ this.pushStackOrRun(unparkTransform);
-+
-+ boolean interuptted = false;
-+ while (!unparkTransform.isReleasable()) {
-+ try {
-+ ForkJoinPool.managedBlock(unparkTransform);
-+ } catch (final InterruptedException ex) {
-+ interuptted = true;
-+ }
-+ }
-+
-+ if (interuptted) {
-+ Thread.currentThread().interrupt();
-+ }
-+
-+ return this.getNow(null);
-+ }
-+
-+ public CompletableFuture toFuture() {
-+ final Object rawResult = this.getResultVolatile();
-+ if (rawResult != null) {
-+ if (rawResult instanceof ExceptionResult exRes) {
-+ return CompletableFuture.failedFuture(exRes.ex);
-+ } else {
-+ return CompletableFuture.completedFuture((T)unmaskNull(rawResult));
-+ }
-+ }
-+
-+ final CompletableFuture ret = new CompletableFuture<>();
-+
-+ class ToFuture implements BiConsumer {
-+
-+ @Override
-+ public void accept(final T res, final Throwable ex) {
-+ if (ex != null) {
-+ ret.completeExceptionally(ex);
-+ } else {
-+ ret.complete(res);
-+ }
-+ }
-+ }
-+
-+ this.whenComplete(new ToFuture());
-+
-+ return ret;
-+ }
-+
-+ public static Completable fromFuture(final CompletionStage stage) {
-+ final Completable ret = new Completable<>();
-+
-+ class FromFuture implements BiConsumer {
-+ @Override
-+ public void accept(final T res, final Throwable ex) {
-+ if (ex != null) {
-+ ret.completeExceptionally(ex);
-+ } else {
-+ ret.complete(res);
-+ }
-+ }
-+ }
-+
-+ stage.whenComplete(new FromFuture());
-+
-+ return ret;
-+ }
-+
-+
-+ public Completable thenApply(final Function super T, ? extends U> function) {
-+ return this.thenApply(function, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable thenApply(final Function super T, ? extends U> function, final Function super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(function, "Function may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable ret = new Completable<>();
-+ this.pushStackOrRun(new ApplyTransform<>(null, this, ret, exceptionHandler, function));
-+ return ret;
-+ }
-+
-+ public Completable thenApplyAsync(final Function super T, ? extends U> function) {
-+ return this.thenApplyAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable thenApplyAsync(final Function super T, ? extends U> function, final Executor executor) {
-+ return this.thenApplyAsync(function, executor, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable thenApplyAsync(final Function super T, ? extends U> function, final Executor executor, final Function super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(function, "Function may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable ret = new Completable<>();
-+ this.pushStackOrRun(new ApplyTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function));
-+ return ret;
-+ }
-+
-+
-+ public Completable thenAccept(final Consumer super T> consumer) {
-+ return this.thenAccept(consumer, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable thenAccept(final Consumer super T> consumer, final Function super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(consumer, "Consumer may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable ret = new Completable<>();
-+ this.pushStackOrRun(new AcceptTransform<>(null, this, ret, exceptionHandler, consumer));
-+ return ret;
-+ }
-+
-+ public Completable thenAcceptAsync(final Consumer super T> consumer) {
-+ return this.thenAcceptAsync(consumer, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable thenAcceptAsync(final Consumer super T> consumer, final Executor executor) {
-+ return this.thenAcceptAsync(consumer, executor, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable thenAcceptAsync(final Consumer super T> consumer, final Executor executor, final Function super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(consumer, "Consumer may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable ret = new Completable<>();
-+ this.pushStackOrRun(new AcceptTransform<>(checkExecutor(executor), this, ret, exceptionHandler, consumer));
-+ return ret;
-+ }
-+
-+
-+ public Completable thenRun(final Runnable run) {
-+ return this.thenRun(run, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable thenRun(final Runnable run, final Function super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(run, "Run may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable ret = new Completable<>();
-+ this.pushStackOrRun(new RunTransform<>(null, this, ret, exceptionHandler, run));
-+ return ret;
-+ }
-+
-+ public Completable thenRunAsync(final Runnable run) {
-+ return this.thenRunAsync(run, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable thenRunAsync(final Runnable run, final Executor executor) {
-+ return this.thenRunAsync(run, executor, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable thenRunAsync(final Runnable run, final Executor executor, final Function super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(run, "Run may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable ret = new Completable<>();
-+ this.pushStackOrRun(new RunTransform<>(checkExecutor(executor), this, ret, exceptionHandler, run));
-+ return ret;
-+ }
-+
-+
-+ public Completable handle(final BiFunction super T, ? super Throwable, ? extends U> function) {
-+ return this.handle(function, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable handle(final BiFunction super T, ? super Throwable, ? extends U> function,
-+ final Function super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(function, "Function may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable ret = new Completable<>();
-+ this.pushStackOrRun(new HandleTransform<>(null, this, ret, exceptionHandler, function));
-+ return ret;
-+ }
-+
-+ public Completable handleAsync(final BiFunction super T, ? super Throwable, ? extends U> function) {
-+ return this.handleAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable handleAsync(final BiFunction super T, ? super Throwable, ? extends U> function,
-+ final Executor executor) {
-+ return this.handleAsync(function, executor, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable handleAsync(final BiFunction super T, ? super Throwable, ? extends U> function,
-+ final Executor executor,
-+ final Function super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(function, "Function may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable ret = new Completable<>();
-+ this.pushStackOrRun(new HandleTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function));
-+ return ret;
-+ }
-+
-+
-+ public Completable whenComplete(final BiConsumer super T, ? super Throwable> consumer) {
-+ return this.whenComplete(consumer, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable whenComplete(final BiConsumer super T, ? super Throwable> consumer, final Function super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(consumer, "Consumer may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable ret = new Completable<>();
-+ this.pushStackOrRun(new WhenTransform<>(null, this, ret, exceptionHandler, consumer));
-+ return ret;
-+ }
-+
-+ public Completable whenCompleteAsync(final BiConsumer super T, ? super Throwable> consumer) {
-+ return this.whenCompleteAsync(consumer, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable whenCompleteAsync(final BiConsumer super T, ? super Throwable> consumer, final Executor executor) {
-+ return this.whenCompleteAsync(consumer, executor, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable whenCompleteAsync(final BiConsumer super T, ? super Throwable> consumer, final Executor executor,
-+ final Function super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(consumer, "Consumer may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable ret = new Completable<>();
-+ this.pushStackOrRun(new WhenTransform<>(checkExecutor(executor), this, ret, exceptionHandler, consumer));
-+ return ret;
-+ }
-+
-+
-+ public Completable exceptionally(final Function function) {
-+ return this.exceptionally(function, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable exceptionally(final Function function, final Function super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(function, "Function may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable ret = new Completable<>();
-+ this.pushStackOrRun(new ExceptionallyTransform<>(null, this, ret, exceptionHandler, function));
-+ return ret;
-+ }
-+
-+ public Completable exceptionallyAsync(final Function function) {
-+ return this.exceptionallyAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable exceptionallyAsync(final Function function, final Executor executor) {
-+ return this.exceptionallyAsync(function, executor, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable exceptionallyAsync(final Function function, final Executor executor,
-+ final Function super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(function, "Function may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable ret = new Completable<>();
-+ this.pushStackOrRun(new ExceptionallyTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function));
-+ return ret;
-+ }
-+
-+ private static final class ExceptionResult {
-+ public final Throwable ex;
-+
-+ public ExceptionResult(final Throwable ex) {
-+ this.ex = ex;
- }
- }
-
-- /**
-- * Adds a waiter that will be completed asynchronously by the complete() calls. If complete()
-- * has already been called, then invokes the consumer synchronously with the completed result.
-- * @param consumer Consumer to be executed on completion
-- * @throws NullPointerException If consumer is null
-- * @return A cancellable which will control the execution of the specified consumer
-- */
-- public Cancellable addWaiter(final BiConsumer consumer) {
-- if (this.waiters.add(consumer)) {
-- return new CancellableImpl(consumer);
-+ private static abstract class Transform implements Runnable, CompletableFuture.AsynchronousCompletionTask {
-+
-+ private Transform, T> next;
-+
-+ private final Executor executor;
-+ protected final Completable from;
-+ protected final Completable to;
-+ protected final Function super Throwable, ? extends Throwable> exceptionHandler;
-+
-+ protected Transform(final Executor executor, final Completable from, final Completable to,
-+ final Function super Throwable, ? extends Throwable> exceptionHandler) {
-+ this.executor = executor;
-+ this.from = from;
-+ this.to = to;
-+ this.exceptionHandler = exceptionHandler;
-+ }
-+
-+ // force interface call to become virtual call
-+ @Override
-+ public abstract void run();
-+
-+ protected void failed(final Throwable throwable) {
-+ Throwable complete;
-+ try {
-+ complete = this.exceptionHandler.apply(throwable);
-+ } catch (final Throwable thr2) {
-+ throwable.addSuppressed(thr2);
-+ complete = throwable;
-+ }
-+ this.to.completeExceptionally(complete);
-+ }
-+
-+ public void execute() {
-+ if (this.executor == null) {
-+ this.run();
-+ return;
-+ }
-+
-+ try {
-+ this.executor.execute(this);
-+ } catch (final Throwable throwable) {
-+ this.failed(throwable);
-+ }
-+ }
-+ }
-+
-+ private static final class ApplyTransform extends Transform {
-+
-+ private final Function super T, ? extends U> function;
-+
-+ public ApplyTransform(final Executor executor, final Completable from, final Completable to,
-+ final Function super Throwable, ? extends Throwable> exceptionHandler,
-+ final Function super T, ? extends U> function) {
-+ super(executor, from, to, exceptionHandler);
-+ this.function = function;
-+ }
-+
-+ @Override
-+ public void run() {
-+ final Object result = this.from.getResultPlain();
-+ try {
-+ if (result instanceof ExceptionResult exRes) {
-+ this.to.completeExceptionally(exRes.ex);
-+ } else {
-+ this.to.complete(this.function.apply((T)unmaskNull(result)));
-+ }
-+ } catch (final Throwable throwable) {
-+ this.failed(throwable);
-+ }
- }
-- this.completeWaiter(consumer, this.result, this.throwable);
-- return new CancellableImpl(consumer);
- }
-
-- public void complete(final T result) {
-- this.result = result;
-- this.completeAllWaiters(result, null);
-+ private static final class AcceptTransform extends Transform {
-+ private final Consumer super T> consumer;
-+
-+ public AcceptTransform(final Executor executor, final Completable from, final Completable to,
-+ final Function super Throwable, ? extends Throwable> exceptionHandler,
-+ final Consumer super T> consumer) {
-+ super(executor, from, to, exceptionHandler);
-+ this.consumer = consumer;
-+ }
-+
-+ @Override
-+ public void run() {
-+ final Object result = this.from.getResultPlain();
-+ try {
-+ if (result instanceof ExceptionResult exRes) {
-+ this.to.completeExceptionally(exRes.ex);
-+ } else {
-+ this.consumer.accept((T)unmaskNull(result));
-+ this.to.complete(null);
-+ }
-+ } catch (final Throwable throwable) {
-+ this.failed(throwable);
-+ }
-+ }
- }
-
-- public void completeWithThrowable(final Throwable throwable) {
-- if (throwable == null) {
-- throw new NullPointerException("Throwable cannot be null");
-+ private static final class RunTransform extends Transform {
-+ private final Runnable run;
-+
-+ public RunTransform(final Executor executor, final Completable from, final Completable to,
-+ final Function super Throwable, ? extends Throwable> exceptionHandler,
-+ final Runnable run) {
-+ super(executor, from, to, exceptionHandler);
-+ this.run = run;
-+ }
-+
-+ @Override
-+ public void run() {
-+ final Object result = this.from.getResultPlain();
-+ try {
-+ if (result instanceof ExceptionResult exRes) {
-+ this.to.completeExceptionally(exRes.ex);
-+ } else {
-+ this.run.run();
-+ this.to.complete(null);
-+ }
-+ } catch (final Throwable throwable) {
-+ this.failed(throwable);
-+ }
- }
-- this.throwable = throwable;
-- this.completeAllWaiters(null, throwable);
- }
-
-- private final class CancellableImpl implements Cancellable {
-+ private static final class HandleTransform extends Transform {
-+
-+ private final BiFunction super T, ? super Throwable, ? extends U> function;
-+
-+ public HandleTransform(final Executor executor, final Completable from, final Completable to,
-+ final Function super Throwable, ? extends Throwable> exceptionHandler,
-+ final BiFunction super T, ? super Throwable, ? extends U> function) {
-+ super(executor, from, to, exceptionHandler);
-+ this.function = function;
-+ }
-
-- private final BiConsumer