aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorSpottedleaf <[email protected]>2024-06-14 10:47:33 -0700
committerSpottedleaf <[email protected]>2024-06-14 10:47:33 -0700
commitf5693896c5d0a3116efedcc730896be2c727038d (patch)
tree6f90be1bed8ad5698fbf3afb01b4da6fa8b5e827
parentdf633e5ffad29b1ecb3260ebba03d0459aa4861a (diff)
downloadPaper-f5693896c5d0a3116efedcc730896be2c727038d.tar.gz
Paper-f5693896c5d0a3116efedcc730896be2c727038d.zip
Update ConcurrentUtil
Mostly for the primitive long to reference hashtable impl
-rw-r--r--patches/server/0007-ConcurrentUtil.patch3914
1 files changed, 3689 insertions, 225 deletions
diff --git a/patches/server/0007-ConcurrentUtil.patch b/patches/server/0007-ConcurrentUtil.patch
index b5d70f9b38..83edf8ab9d 100644
--- a/patches/server/0007-ConcurrentUtil.patch
+++ b/patches/server/0007-ConcurrentUtil.patch
@@ -6,14 +6,15 @@ Subject: [PATCH] ConcurrentUtil
diff --git a/src/main/java/ca/spottedleaf/concurrentutil/collection/MultiThreadedQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/collection/MultiThreadedQueue.java
new file mode 100644
-index 0000000000000000000000000000000000000000..f4415f782b32fed25da98e44b172f717c4d46e34
+index 0000000000000000000000000000000000000000..f84a622dc29750139ac280f480b7cd132b036287
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/concurrentutil/collection/MultiThreadedQueue.java
-@@ -0,0 +1,1402 @@
+@@ -0,0 +1,1421 @@
+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.ArrayList;
+import java.util.Collection;
@@ -405,6 +406,24 @@ index 0000000000000000000000000000000000000000..f4415f782b32fed25da98e44b172f717
+ }
+
+ /**
++ * Returns whether this queue is currently add-blocked. That is, whether {@link #add(Object)} and friends will return {@code false}.
++ */
++ public boolean isAddBlocked() {
++ for (LinkedNode<E> tail = this.getTailOpaque();;) {
++ LinkedNode<E> next = tail.getNextVolatile();
++ if (next == null) {
++ return false;
++ }
++
++ if (next == tail) {
++ return true;
++ }
++
++ tail = next;
++ }
++ }
++
++ /**
+ * Atomically removes the head from this queue if it exists, otherwise prevents additions to this queue if no
+ * head is removed.
+ * <p>
@@ -1414,14 +1433,15 @@ index 0000000000000000000000000000000000000000..f4415f782b32fed25da98e44b172f717
+}
diff --git a/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java
new file mode 100644
-index 0000000000000000000000000000000000000000..597659f38aa816646dcda4ca39c002b6d9f9a792
+index 0000000000000000000000000000000000000000..094eff418b4e3bffce020d650931b4d9e58fa9ed
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/concurrentutil/collection/SRSWLinkedQueue.java
-@@ -0,0 +1,148 @@
+@@ -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;
+
@@ -1568,22 +1588,22 @@ index 0000000000000000000000000000000000000000..597659f38aa816646dcda4ca39c002b6
+}
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..a1ad3308f9c3545a604b635896259a1cd3382b2a
+index 0000000000000000000000000000000000000000..46d1bd01542ebeeffc0006a5c585a50dbbbff907
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java
-@@ -0,0 +1,98 @@
+@@ -0,0 +1,112 @@
+package ca.spottedleaf.concurrentutil.completable;
+
+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
+import ca.spottedleaf.concurrentutil.executor.Cancellable;
+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
-+import com.mojang.logging.LogUtils;
+import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
+import java.util.function.BiConsumer;
+
+public final class Completable<T> {
+
-+ private static final Logger LOGGER = LogUtils.getLogger();
++ private static final Logger LOGGER = LoggerFactory.getLogger(Completable.class);
+
+ private final MultiThreadedQueue<BiConsumer<T, Throwable>> waiters = new MultiThreadedQueue<>();
+ private T result;
@@ -1610,6 +1630,13 @@ index 0000000000000000000000000000000000000000..a1ad3308f9c3545a604b635896259a1c
+ 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<T, Throwable> consumer) {
+ if (this.waiters.add(consumer)) {
+ return new CancellableImpl(consumer);
@@ -1635,6 +1662,13 @@ index 0000000000000000000000000000000000000000..a1ad3308f9c3545a604b635896259a1c
+ }
+ }
+
++ /**
++ * 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<T, Throwable> consumer) {
+ if (this.waiters.add(consumer)) {
+ return new CancellableImpl(consumer);
@@ -1672,16 +1706,32 @@ index 0000000000000000000000000000000000000000..a1ad3308f9c3545a604b635896259a1c
+}
diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java
new file mode 100644
-index 0000000000000000000000000000000000000000..8c452b0988da4725762d543f6bee09915c328ae6
+index 0000000000000000000000000000000000000000..18d646676fd022afd64afaac30ec1bd283a73b0e
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/BaseExecutor.java
-@@ -0,0 +1,198 @@
+@@ -0,0 +1,208 @@
+package ca.spottedleaf.concurrentutil.executor;
+
-+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
+import java.util.function.BooleanSupplier;
+
++/**
++ * Base implementation for an abstract queue of tasks which are executed either synchronously or asynchronously.
++ *
++ * <p>
++ * The implementation supports tracking task executions using {@link #getTotalTasksScheduled()} and
++ * {@link #getTotalTasksExecuted()}, and optionally shutting down the executor using {@link #shutdown()}
++ * </p>
++ *
++ * <p>
++ * 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
++ * <i>queue</i> a task or <i>create</i> a task. A <i>queued</i> task is one which will eventually be executed,
++ * and a <i>created</i> 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.
++ * </p>
++ */
+public interface BaseExecutor {
+
+ /**
@@ -1710,7 +1760,6 @@ index 0000000000000000000000000000000000000000..8c452b0988da4725762d543f6bee0991
+ */
+ public long getTotalTasksExecuted();
+
-+
+ /**
+ * Waits until this queue has had all of its tasks executed (NOT removed). See {@link #haveAllTasksExecuted()}
+ * <p>
@@ -1723,7 +1772,7 @@ index 0000000000000000000000000000000000000000..8c452b0988da4725762d543f6bee0991
+ * time than expected. Effectively, do not rely on this call being fast - even if there are few tasks scheduled.
+ * </p>
+ * <p>
-+ * Note: Interruptions to the the current thread have no effect. Interrupt status is also not affected by this cal.
++ * Note: Interruptions to the the current thread have no effect. Interrupt status is also not affected by this call.
+ * </p>
+ *
+ * @throws IllegalStateException If the current thread is not allowed to wait
@@ -1739,17 +1788,6 @@ index 0000000000000000000000000000000000000000..8c452b0988da4725762d543f6bee0991
+
+ /**
+ * Executes the next available task.
-+ * <p>
-+ * If there is a task with priority {@link PrioritisedExecutor.Priority#BLOCKING} available, then that such task is executed.
-+ * </p>
-+ * <p>
-+ * If there is a task with priority {@link PrioritisedExecutor.Priority#IDLE} available then that task is only executed
-+ * when there are no other tasks available with a higher priority.
-+ * </p>
-+ * <p>
-+ * If there are no tasks that have priority {@link PrioritisedExecutor.Priority#BLOCKING} or {@link PrioritisedExecutor.Priority#IDLE}, then
-+ * this function will be biased to execute tasks that have higher priorities.
-+ * </p>
+ *
+ * @return {@code true} if a task was executed, {@code false} otherwise
+ * @throws IllegalStateException If the current thread is not allowed to execute a task
@@ -1791,12 +1829,12 @@ index 0000000000000000000000000000000000000000..8c452b0988da4725762d543f6bee0991
+ }
+
+ /**
-+ * Waits and executes tasks until the condition returns {@code true} or {@code System.nanoTime() >= deadline}.
++ * 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) && !condition.getAsBoolean() && (System.nanoTime() < deadline)) {
++ while ((System.nanoTime() - deadline < 0L) && !condition.getAsBoolean() && (System.nanoTime() - deadline < 0L)) {
+ if (this.executeTask()) {
+ failures = failures >>> 2;
+ } else {
@@ -1806,11 +1844,11 @@ index 0000000000000000000000000000000000000000..8c452b0988da4725762d543f6bee0991
+ }
+
+ /**
-+ * Waits and executes tasks until {@code System.nanoTime() >= deadline}.
++ * Waits and executes tasks until {@code System.nanoTime() - deadline >= 0}.
+ */
+ public default void executeUntil(final long deadline) {
+ long failures = 0;
-+ while (System.nanoTime() < deadline) {
++ while (System.nanoTime() - deadline < 0L) {
+ if (this.executeTask()) {
+ failures = failures >>> 2;
+ } else {
@@ -1831,6 +1869,7 @@ index 0000000000000000000000000000000000000000..8c452b0988da4725762d543f6bee0991
+ *
+ * @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();
@@ -1838,13 +1877,18 @@ index 0000000000000000000000000000000000000000..8c452b0988da4725762d543f6bee0991
+
+ /**
+ * Returns whether this queue has shut down. Effectively, whether new tasks will be rejected - this method
-+ * does not indicate whether all of the tasks scheduled have been executed.
++ * 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 {
+
+ /**
@@ -2072,14 +2116,18 @@ index 0000000000000000000000000000000000000000..3ce10053d4ec51855ad7012abb5d97df
+}
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..e5d8ff730ba9d83efc2d80782de313a718bf55b3
+index 0000000000000000000000000000000000000000..91beb6f23f257cf265fe3150f760892e605f217a
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedExecutor.java
-@@ -0,0 +1,246 @@
+@@ -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 {
@@ -2141,12 +2189,12 @@ index 0000000000000000000000000000000000000000..e5d8ff730ba9d83efc2d80782de313a7
+ }
+
+ // returns the higher priority of the two
-+ public static PrioritisedExecutor.Priority max(final Priority p1, final Priority p2) {
++ 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 PrioritisedExecutor.Priority min(final Priority p1, final Priority p2) {
++ public static Priority min(final Priority p1, final Priority p2) {
+ return p1.isLowerOrEqualPriority(p2) ? p1 : p2;
+ }
+
@@ -2198,14 +2246,14 @@ index 0000000000000000000000000000000000000000..e5d8ff730ba9d83efc2d80782de313a7
+ return priority > than;
+ }
+
-+ static final PrioritisedExecutor.Priority[] PRIORITIES = PrioritisedExecutor.Priority.values();
++ 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 PrioritisedExecutor.Priority getPriority(final int priority) {
++ public static Priority getPriority(final int priority) {
+ return PRIORITIES[priority + 1];
+ }
+
@@ -2227,6 +2275,26 @@ index 0000000000000000000000000000000000000000..e5d8ff730ba9d83efc2d80782de313a7
+ }
+
+ /**
++ * Executes the next available task.
++ * <p>
++ * If there is a task with priority {@link PrioritisedExecutor.Priority#BLOCKING} available, then that such task is executed.
++ * </p>
++ * <p>
++ * If there is a task with priority {@link PrioritisedExecutor.Priority#IDLE} available then that task is only executed
++ * when there are no other tasks available with a higher priority.
++ * </p>
++ * <p>
++ * If there are no tasks that have priority {@link PrioritisedExecutor.Priority#BLOCKING} or {@link PrioritisedExecutor.Priority#IDLE}, then
++ * this function will be biased to execute tasks that have higher priorities.
++ * </p>
++ *
++ * @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;
++
++ /**
+ * Queues or executes a task at {@link Priority#NORMAL} priority.
+ * @param task The task to run.
+ *
@@ -2236,7 +2304,7 @@ index 0000000000000000000000000000000000000000..e5d8ff730ba9d83efc2d80782de313a7
+ * associated with the parameter
+ */
+ public default PrioritisedTask queueRunnable(final Runnable task) {
-+ return this.queueRunnable(task, PrioritisedExecutor.Priority.NORMAL);
++ return this.queueRunnable(task, Priority.NORMAL);
+ }
+
+ /**
@@ -2251,10 +2319,10 @@ index 0000000000000000000000000000000000000000..e5d8ff730ba9d83efc2d80782de313a7
+ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task
+ * associated with the parameter
+ */
-+ public PrioritisedTask queueRunnable(final Runnable task, final PrioritisedExecutor.Priority priority);
++ public PrioritisedTask queueRunnable(final Runnable task, final Priority priority);
+
+ /**
-+ * Creates, but does not execute or queue the task. The task must later be queued via {@link BaseExecutor.BaseTask#queue()}.
++ * Creates, but does not execute or queue the task. The task must later be queued via {@link BaseTask#queue()}.
+ *
+ * @param task The task to run.
+ *
@@ -2264,12 +2332,12 @@ index 0000000000000000000000000000000000000000..e5d8ff730ba9d83efc2d80782de313a7
+ * @throws UnsupportedOperationException If this executor does not support lazily queueing tasks
+ * @return The prioritised task associated with the parameters
+ */
-+ public default PrioritisedExecutor.PrioritisedTask createTask(final Runnable task) {
-+ return this.createTask(task, PrioritisedExecutor.Priority.NORMAL);
++ public default PrioritisedTask createTask(final Runnable task) {
++ return this.createTask(task, Priority.NORMAL);
+ }
+
+ /**
-+ * Creates, but does not execute or queue the task. The task must later be queued via {@link BaseExecutor.BaseTask#queue()}.
++ * Creates, but does not execute or queue the task. The task must later be queued via {@link BaseTask#queue()}.
+ *
+ * @param task The task to run.
+ * @param priority The priority for the task.
@@ -2280,15 +2348,21 @@ index 0000000000000000000000000000000000000000..e5d8ff730ba9d83efc2d80782de313a7
+ * @throws UnsupportedOperationException If this executor does not support lazily queueing tasks
+ * @return The prioritised task associated with the parameters
+ */
-+ public PrioritisedExecutor.PrioritisedTask createTask(final Runnable task, final PrioritisedExecutor.Priority priority);
++ 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.
++ *
++ * @see ca.spottedleaf.concurrentutil.executor.BaseExecutor.BaseTask
++ */
+ public static interface PrioritisedTask extends BaseTask {
+
+ /**
-+ * Returns the current priority. Note that {@link PrioritisedExecutor.Priority#COMPLETING} will be returned
++ * Returns the current priority. Note that {@link Priority#COMPLETING} will be returned
+ * if this task is completing or has completed.
+ */
-+ public PrioritisedExecutor.Priority getPriority();
++ public Priority getPriority();
+
+ /**
+ * Attempts to set this task's priority level to the level specified.
@@ -2299,7 +2373,7 @@ index 0000000000000000000000000000000000000000..e5d8ff730ba9d83efc2d80782de313a7
+ * @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.
+ */
-+ public boolean setPriority(final PrioritisedExecutor.Priority priority);
++ public boolean setPriority(final Priority priority);
+
+ /**
+ * Attempts to raise the priority to the priority level specified.
@@ -2309,7 +2383,7 @@ index 0000000000000000000000000000000000000000..e5d8ff730ba9d83efc2d80782de313a7
+ * @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.
+ */
-+ public boolean raisePriority(final PrioritisedExecutor.Priority priority);
++ public boolean raisePriority(final Priority priority);
+
+ /**
+ * Attempts to lower the priority to the priority level specified.
@@ -2319,20 +2393,20 @@ index 0000000000000000000000000000000000000000..e5d8ff730ba9d83efc2d80782de313a7
+ * @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.
+ */
-+ public boolean lowerPriority(final PrioritisedExecutor.Priority priority);
++ public boolean lowerPriority(final Priority priority);
+ }
+}
diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java
new file mode 100644
-index 0000000000000000000000000000000000000000..91fe0f7049122f62f05ba09c24cba5d758340cff
+index 0000000000000000000000000000000000000000..d1683ba6350e530373944f98192c0f2baf241e70
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedQueueExecutorThread.java
-@@ -0,0 +1,297 @@
+@@ -0,0 +1,301 @@
+package ca.spottedleaf.concurrentutil.executor.standard;
+
+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
-+import com.mojang.logging.LogUtils;
+import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
+import java.lang.invoke.VarHandle;
+import java.util.concurrent.locks.LockSupport;
+
@@ -2347,14 +2421,14 @@ index 0000000000000000000000000000000000000000..91fe0f7049122f62f05ba09c24cba5d7
+ */
+public class PrioritisedQueueExecutorThread extends Thread implements PrioritisedExecutor {
+
-+ private static final Logger LOGGER = LogUtils.getLogger();
++ private static final Logger LOGGER = LoggerFactory.getLogger(PrioritisedQueueExecutorThread.class);
+
+ protected final PrioritisedExecutor queue;
+
+ protected volatile boolean threadShutdown;
+
-+ protected static final VarHandle THREAD_PARKED_HANDLE = ConcurrentUtil.getVarHandle(PrioritisedQueueExecutorThread.class, "threadParked", boolean.class);
+ protected volatile boolean threadParked;
++ protected static final VarHandle THREAD_PARKED_HANDLE = ConcurrentUtil.getVarHandle(PrioritisedQueueExecutorThread.class, "threadParked", boolean.class);
+
+ protected volatile boolean halted;
+
@@ -2429,6 +2503,10 @@ index 0000000000000000000000000000000000000000..91fe0f7049122f62f05ba09c24cba5d7
+ }
+ }
+
++ /**
++ * Attempts to poll as many tasks as possible, returning when finished.
++ * @return Whether any tasks were executed.
++ */
+ protected boolean pollTasks() {
+ boolean ret = false;
+
@@ -2473,7 +2551,7 @@ index 0000000000000000000000000000000000000000..91fe0f7049122f62f05ba09c24cba5d7
+
+ @Override
+ public PrioritisedTask createTask(final Runnable task, final Priority priority) {
-+ final PrioritisedExecutor.PrioritisedTask queueTask = this.queue.createTask(task, priority);
++ final PrioritisedTask queueTask = this.queue.createTask(task, priority);
+
+ // need to override queue() to notify us of tasks
+ return new PrioritisedTask() {
@@ -2519,8 +2597,8 @@ index 0000000000000000000000000000000000000000..91fe0f7049122f62f05ba09c24cba5d7
+ }
+
+ @Override
-+ public PrioritisedExecutor.PrioritisedTask queueRunnable(final Runnable task, final PrioritisedExecutor.Priority priority) {
-+ final PrioritisedExecutor.PrioritisedTask ret = this.queue.queueRunnable(task, priority);
++ public PrioritisedTask queueRunnable(final Runnable task, final Priority priority) {
++ final PrioritisedTask ret = this.queue.queueRunnable(task, priority);
+
+ this.notifyTasks();
+
@@ -2627,15 +2705,15 @@ index 0000000000000000000000000000000000000000..91fe0f7049122f62f05ba09c24cba5d7
+}
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..26fa2caa18a9194e57574a4a7fa9f7a4265740e0
+index 0000000000000000000000000000000000000000..2ba36e29d0d8693f2f5e6c6d195ca27f2a5099aa
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java
-@@ -0,0 +1,579 @@
+@@ -0,0 +1,632 @@
+package ca.spottedleaf.concurrentutil.executor.standard;
+
-+import com.mojang.logging.LogUtils;
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
+import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
@@ -2645,30 +2723,46 @@ index 0000000000000000000000000000000000000000..26fa2caa18a9194e57574a4a7fa9f7a4
+
+public final class PrioritisedThreadPool {
+
-+ private static final Logger LOGGER = LogUtils.getLogger();
++ private static final Logger LOGGER = LoggerFactory.getLogger(PrioritisedThreadPool.class);
+
-+ protected final PrioritisedThread[] threads;
-+ protected final TreeSet<PrioritisedPoolExecutorImpl> queues = new TreeSet<>(PrioritisedPoolExecutorImpl.comparator());
-+ protected final String name;
-+ protected final long queueMaxHoldTime;
++ private final PrioritisedThread[] threads;
++ private final TreeSet<PrioritisedPoolExecutorImpl> queues = new TreeSet<>(PrioritisedPoolExecutorImpl.comparator());
++ private final String name;
++ private final long queueMaxHoldTime;
+
-+ protected final ReferenceOpenHashSet<PrioritisedPoolExecutorImpl> nonShutdownQueues = new ReferenceOpenHashSet<>();
-+ protected final ReferenceOpenHashSet<PrioritisedPoolExecutorImpl> activeQueues = new ReferenceOpenHashSet<>();
++ private final ReferenceOpenHashSet<PrioritisedPoolExecutorImpl> nonShutdownQueues = new ReferenceOpenHashSet<>();
++ private final ReferenceOpenHashSet<PrioritisedPoolExecutorImpl> activeQueues = new ReferenceOpenHashSet<>();
+
-+ protected boolean shutdown;
++ private boolean shutdown;
+
-+ protected long schedulingIdGenerator;
++ private long schedulingIdGenerator;
+
-+ protected static final long DEFAULT_QUEUE_HOLD_TIME = (long)(5.0e6);
++ 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<Thread, Integer> 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<Thread, Integer> threadModifier,
+ final long queueHoldTime) { // in ns
+ if (threads <= 0) {
@@ -2700,16 +2794,32 @@ index 0000000000000000000000000000000000000000..26fa2caa18a9194e57574a4a7fa9f7a4
+ }
+ }
+
++ /**
++ * Returns an array representing the threads backing this thread pool.
++ */
+ public Thread[] getThreads() {
+ return Arrays.copyOf(this.threads, this.threads.length, Thread[].class);
+ }
+
-+ public PrioritisedPoolExecutor createExecutor(final String name, final int parallelism) {
++ /**
++ * 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));
++ 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)
++ );
+
+ this.nonShutdownQueues.add(ret);
+
@@ -2805,6 +2915,12 @@ index 0000000000000000000000000000000000000000..26fa2caa18a9194e57574a4a7fa9f7a4
+ }
+ }
+
++ /**
++ * 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
++ * thread pool.
++ * @param wait Whether to wait for tasks to be executed
++ */
+ public void shutdown(final boolean wait) {
+ final ArrayList<PrioritisedPoolExecutorImpl> queuesToShutdown;
+ synchronized (this.nonShutdownQueues) {
@@ -2965,12 +3081,14 @@ index 0000000000000000000000000000000000000000..26fa2caa18a9194e57574a4a7fa9f7a4
+
+ 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) {
++ 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;
+ }
+
+ public static Comparator<PrioritisedPoolExecutorImpl> comparator() {
@@ -2979,6 +3097,19 @@ index 0000000000000000000000000000000000000000..26fa2caa18a9194e57574a4a7fa9f7a4
+ 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) {
@@ -3212,7 +3343,7 @@ index 0000000000000000000000000000000000000000..26fa2caa18a9194e57574a4a7fa9f7a4
+}
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..b71404be2c82f7db35272b367af861e94d6c73d3
+index 0000000000000000000000000000000000000000..3e8401b1b1f833c4f01bc87059a2f48d761d989f
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadedTaskQueue.java
@@ -0,0 +1,378 @@
@@ -3239,8 +3370,8 @@ index 0000000000000000000000000000000000000000..b71404be2c82f7db35272b367af861e9
+ protected long taskIdGenerator = 0;
+
+ @Override
-+ public PrioritisedExecutor.PrioritisedTask queueRunnable(final Runnable task, final PrioritisedExecutor.Priority priority) throws IllegalStateException, IllegalArgumentException {
-+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
++ 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) {
@@ -3273,7 +3404,7 @@ index 0000000000000000000000000000000000000000..b71404be2c82f7db35272b367af861e9
+
+ @Override
+ public PrioritisedExecutor.PrioritisedTask createTask(final Runnable task, final Priority priority) {
-+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
++ if (!Priority.isValidPriority(priority)) {
+ throw new IllegalArgumentException("Priority " + priority + " is invalid");
+ }
+ if (task == null) {
@@ -3305,7 +3436,7 @@ index 0000000000000000000000000000000000000000..b71404be2c82f7db35272b367af861e9
+ return this.poll(Priority.IDLE);
+ }
+
-+ protected PrioritisedTask poll(final PrioritisedExecutor.Priority minPriority) {
++ protected PrioritisedTask poll(final Priority minPriority) {
+ final ArrayDeque<PrioritisedTask>[] queues = this.queues;
+ synchronized (queues) {
+ final int max = minPriority.priority;
@@ -3382,10 +3513,10 @@ index 0000000000000000000000000000000000000000..b71404be2c82f7db35272b367af861e9
+ protected static final long NOT_SCHEDULED_ID = -1L;
+
+ protected Runnable runnable;
-+ protected volatile PrioritisedExecutor.Priority priority;
++ protected volatile Priority priority;
+
-+ protected PrioritisedTask(final long id, final Runnable runnable, final PrioritisedExecutor.Priority priority, final PrioritisedThreadedTaskQueue queue) {
-+ if (!PrioritisedExecutor.Priority.isValidPriority(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);
+ }
+
@@ -3395,8 +3526,8 @@ index 0000000000000000000000000000000000000000..b71404be2c82f7db35272b367af861e9
+ this.id = id;
+ }
+
-+ protected PrioritisedTask(final Runnable runnable, final PrioritisedExecutor.Priority priority, final PrioritisedThreadedTaskQueue queue) {
-+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
++ protected PrioritisedTask(final Runnable runnable, final Priority priority, final PrioritisedThreadedTaskQueue queue) {
++ if (!Priority.isValidPriority(priority)) {
+ throw new IllegalArgumentException("Invalid priority " + priority);
+ }
+
@@ -3417,8 +3548,8 @@ index 0000000000000000000000000000000000000000..b71404be2c82f7db35272b367af861e9
+ throw new IllegalStateException("Queue has shutdown");
+ }
+
-+ final PrioritisedExecutor.Priority priority = this.priority;
-+ if (priority == PrioritisedExecutor.Priority.COMPLETING) {
++ final Priority priority = this.priority;
++ if (priority == Priority.COMPLETING) {
+ return false;
+ }
+
@@ -3437,11 +3568,11 @@ index 0000000000000000000000000000000000000000..b71404be2c82f7db35272b367af861e9
+ }
+
+ protected boolean trySetCompleting(final int minPriority) {
-+ final PrioritisedExecutor.Priority oldPriority = this.priority;
-+ if (oldPriority != PrioritisedExecutor.Priority.COMPLETING && oldPriority.isHigherOrEqualPriority(minPriority)) {
-+ this.priority = PrioritisedExecutor.Priority.COMPLETING;
++ 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, PrioritisedExecutor.Priority.COMPLETING);
++ this.queue.priorityChange(this, oldPriority, Priority.COMPLETING);
+ }
+ return true;
+ }
@@ -3450,19 +3581,19 @@ index 0000000000000000000000000000000000000000..b71404be2c82f7db35272b367af861e9
+ }
+
+ @Override
-+ public PrioritisedExecutor.Priority getPriority() {
++ public Priority getPriority() {
+ return this.priority;
+ }
+
+ @Override
-+ public boolean setPriority(final PrioritisedExecutor.Priority priority) {
-+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
++ public boolean setPriority(final Priority priority) {
++ if (!Priority.isValidPriority(priority)) {
+ throw new IllegalArgumentException("Invalid priority " + priority);
+ }
+ synchronized (this.queue.queues) {
-+ final PrioritisedExecutor.Priority curr = this.priority;
++ final Priority curr = this.priority;
+
-+ if (curr == PrioritisedExecutor.Priority.COMPLETING) {
++ if (curr == Priority.COMPLETING) {
+ return false;
+ }
+
@@ -3483,15 +3614,15 @@ index 0000000000000000000000000000000000000000..b71404be2c82f7db35272b367af861e9
+ }
+
+ @Override
-+ public boolean raisePriority(final PrioritisedExecutor.Priority priority) {
-+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
++ public boolean raisePriority(final Priority priority) {
++ if (!Priority.isValidPriority(priority)) {
+ throw new IllegalArgumentException("Invalid priority " + priority);
+ }
+
+ synchronized (this.queue.queues) {
-+ final PrioritisedExecutor.Priority curr = this.priority;
++ final Priority curr = this.priority;
+
-+ if (curr == PrioritisedExecutor.Priority.COMPLETING) {
++ if (curr == Priority.COMPLETING) {
+ return false;
+ }
+
@@ -3512,15 +3643,15 @@ index 0000000000000000000000000000000000000000..b71404be2c82f7db35272b367af861e9
+ }
+
+ @Override
-+ public boolean lowerPriority(final PrioritisedExecutor.Priority priority) {
-+ if (!PrioritisedExecutor.Priority.isValidPriority(priority)) {
++ public boolean lowerPriority(final Priority priority) {
++ if (!Priority.isValidPriority(priority)) {
+ throw new IllegalArgumentException("Invalid priority " + priority);
+ }
+
+ synchronized (this.queue.queues) {
-+ final PrioritisedExecutor.Priority curr = this.priority;
++ final Priority curr = this.priority;
+
-+ if (curr == PrioritisedExecutor.Priority.COMPLETING) {
++ if (curr == Priority.COMPLETING) {
+ return false;
+ }
+
@@ -3545,14 +3676,14 @@ index 0000000000000000000000000000000000000000..b71404be2c82f7db35272b367af861e9
+ final long id;
+ synchronized (this.queue.queues) {
+ final Priority oldPriority = this.priority;
-+ if (oldPriority == PrioritisedExecutor.Priority.COMPLETING) {
++ if (oldPriority == Priority.COMPLETING) {
+ return false;
+ }
+
-+ this.priority = PrioritisedExecutor.Priority.COMPLETING;
++ this.priority = Priority.COMPLETING;
+ // call priority change callback
+ if ((id = this.id) != NOT_SCHEDULED_ID) {
-+ this.queue.priorityChange(this, oldPriority, PrioritisedExecutor.Priority.COMPLETING);
++ this.queue.priorityChange(this, oldPriority, Priority.COMPLETING);
+ }
+ }
+ this.runnable = null;
@@ -3578,14 +3709,14 @@ index 0000000000000000000000000000000000000000..b71404be2c82f7db35272b367af861e9
+ public boolean execute() {
+ synchronized (this.queue.queues) {
+ final Priority oldPriority = this.priority;
-+ if (oldPriority == PrioritisedExecutor.Priority.COMPLETING) {
++ if (oldPriority == Priority.COMPLETING) {
+ return false;
+ }
+
-+ this.priority = PrioritisedExecutor.Priority.COMPLETING;
++ this.priority = Priority.COMPLETING;
+ // call priority change callback
+ if (this.id != NOT_SCHEDULED_ID) {
-+ this.queue.priorityChange(this, oldPriority, PrioritisedExecutor.Priority.COMPLETING);
++ this.queue.priorityChange(this, oldPriority, Priority.COMPLETING);
+ }
+ }
+
@@ -3594,19 +3725,2090 @@ index 0000000000000000000000000000000000000000..b71404be2c82f7db35272b367af861e9
+ }
+ }
+}
+diff --git a/src/main/java/ca/spottedleaf/concurrentutil/function/BiLong1Function.java b/src/main/java/ca/spottedleaf/concurrentutil/function/BiLong1Function.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..94bfd7c56ffcea7d6491e94a7804bc3bd60fe9c3
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/concurrentutil/function/BiLong1Function.java
+@@ -0,0 +1,8 @@
++package ca.spottedleaf.concurrentutil.function;
++
++@FunctionalInterface
++public interface BiLong1Function<T, R> {
++
++ public R apply(final long t1, final T t2);
++
++}
+diff --git a/src/main/java/ca/spottedleaf/concurrentutil/function/BiLongObjectConsumer.java b/src/main/java/ca/spottedleaf/concurrentutil/function/BiLongObjectConsumer.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..8e7eef07960a18d0593688eba55adfa1c85efadf
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/concurrentutil/function/BiLongObjectConsumer.java
+@@ -0,0 +1,8 @@
++package ca.spottedleaf.concurrentutil.function;
++
++@FunctionalInterface
++public interface BiLongObjectConsumer<V> {
++
++ public void accept(final long key, final V value);
++
++}
+diff --git a/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java b/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..7ffe4379b06c03c56abbcbdee3bb720894a10702
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/concurrentutil/lock/ReentrantAreaLock.java
+@@ -0,0 +1,350 @@
++package ca.spottedleaf.concurrentutil.lock;
++
++import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
++import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable;
++import ca.spottedleaf.concurrentutil.util.IntPairUtil;
++import java.util.Objects;
++import java.util.concurrent.locks.LockSupport;
++
++public final class ReentrantAreaLock {
++
++ public final int coordinateShift;
++
++ // aggressive load factor to reduce contention
++ private final ConcurrentLong2ReferenceChainedHashTable<Node> nodes = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(128, 0.2f);
++
++ public ReentrantAreaLock(final int coordinateShift) {
++ this.coordinateShift = coordinateShift;
++ }
++
++ public boolean isHeldByCurrentThread(final int x, final int z) {
++ final Thread currThread = Thread.currentThread();
++ final int shift = this.coordinateShift;
++ final int sectionX = x >> shift;
++ final int sectionZ = z >> shift;
++
++ final long coordinate = IntPairUtil.key(sectionX, sectionZ);
++ final Node node = this.nodes.get(coordinate);
++
++ return node != null && node.thread == currThread;
++ }
++
++ public boolean isHeldByCurrentThread(final int centerX, final int centerZ, final int radius) {
++ return this.isHeldByCurrentThread(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius);
++ }
++
++ public boolean isHeldByCurrentThread(final int fromX, final int fromZ, final int toX, final int toZ) {
++ if (fromX > toX || fromZ > toZ) {
++ throw new IllegalArgumentException();
++ }
++
++ final Thread currThread = Thread.currentThread();
++ final int shift = this.coordinateShift;
++ final int fromSectionX = fromX >> shift;
++ final int fromSectionZ = fromZ >> shift;
++ final int toSectionX = toX >> shift;
++ final int toSectionZ = toZ >> shift;
++
++ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) {
++ for (int currX = fromSectionX; currX <= toSectionX; ++currX) {
++ final long coordinate = IntPairUtil.key(currX, currZ);
++
++ final Node node = this.nodes.get(coordinate);
++
++ if (node == null || node.thread != currThread) {
++ return false;
++ }
++ }
++ }
++
++ return true;
++ }
++
++ public Node tryLock(final int x, final int z) {
++ return this.tryLock(x, z, x, z);
++ }
++
++ public Node tryLock(final int centerX, final int centerZ, final int radius) {
++ return this.tryLock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius);
++ }
++
++ public Node tryLock(final int fromX, final int fromZ, final int toX, final int toZ) {
++ if (fromX > toX || fromZ > toZ) {
++ throw new IllegalArgumentException();
++ }
++
++ final Thread currThread = Thread.currentThread();
++ final int shift = this.coordinateShift;
++ final int fromSectionX = fromX >> shift;
++ final int fromSectionZ = fromZ >> shift;
++ final int toSectionX = toX >> shift;
++ final int toSectionZ = toZ >> shift;
++
++ final long[] areaAffected = new long[(toSectionX - fromSectionX + 1) * (toSectionZ - fromSectionZ + 1)];
++ int areaAffectedLen = 0;
++
++ final Node ret = new Node(this, areaAffected, currThread);
++
++ boolean failed = false;
++
++ // try to fast acquire area
++ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) {
++ for (int currX = fromSectionX; currX <= toSectionX; ++currX) {
++ final long coordinate = IntPairUtil.key(currX, currZ);
++
++ final Node prev = this.nodes.putIfAbsent(coordinate, ret);
++
++ if (prev == null) {
++ areaAffected[areaAffectedLen++] = coordinate;
++ continue;
++ }
++
++ if (prev.thread != currThread) {
++ failed = true;
++ break;
++ }
++ }
++ }
++
++ if (!failed) {
++ return ret;
++ }
++
++ // failed, undo logic
++ if (areaAffectedLen != 0) {
++ for (int i = 0; i < areaAffectedLen; ++i) {
++ final long key = areaAffected[i];
++
++ if (this.nodes.remove(key) != ret) {
++ throw new IllegalStateException();
++ }
++ }
++
++ areaAffectedLen = 0;
++
++ // since we inserted, we need to drain waiters
++ Thread unpark;
++ while ((unpark = ret.pollOrBlockAdds()) != null) {
++ LockSupport.unpark(unpark);
++ }
++ }
++
++ return null;
++ }
++
++ public Node lock(final int x, final int z) {
++ final Thread currThread = Thread.currentThread();
++ final int shift = this.coordinateShift;
++ final int sectionX = x >> shift;
++ final int sectionZ = z >> shift;
++
++ final long coordinate = IntPairUtil.key(sectionX, sectionZ);
++ final long[] areaAffected = new long[1];
++ areaAffected[0] = coordinate;
++
++ final Node ret = new Node(this, areaAffected, currThread);
++
++ for (long failures = 0L;;) {
++ final Node park;
++
++ // try to fast acquire area
++ {
++ final Node prev = this.nodes.putIfAbsent(coordinate, ret);
++
++ if (prev == null) {
++ ret.areaAffectedLen = 1;
++ return ret;
++ } else if (prev.thread != currThread) {
++ park = prev;
++ } else {
++ // only one node we would want to acquire, and it's owned by this thread already
++ // areaAffectedLen = 0 already
++ return ret;
++ }
++ }
++
++ ++failures;
++
++ if (failures > 128L && park.add(currThread)) {
++ LockSupport.park();
++ } else {
++ // high contention, spin wait
++ if (failures < 128L) {
++ for (long i = 0; i < failures; ++i) {
++ Thread.onSpinWait();
++ }
++ failures = failures << 1;
++ } else if (failures < 1_200L) {
++ LockSupport.parkNanos(1_000L);
++ failures = failures + 1L;
++ } else { // scale 0.1ms (100us) per failure
++ Thread.yield();
++ LockSupport.parkNanos(100_000L * failures);
++ failures = failures + 1L;
++ }
++ }
++ }
++ }
++
++ public Node lock(final int centerX, final int centerZ, final int radius) {
++ return this.lock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius);
++ }
++
++ public Node lock(final int fromX, final int fromZ, final int toX, final int toZ) {
++ if (fromX > toX || fromZ > toZ) {
++ throw new IllegalArgumentException();
++ }
++
++ final Thread currThread = Thread.currentThread();
++ final int shift = this.coordinateShift;
++ final int fromSectionX = fromX >> shift;
++ final int fromSectionZ = fromZ >> shift;
++ final int toSectionX = toX >> shift;
++ final int toSectionZ = toZ >> shift;
++
++ if (((fromSectionX ^ toSectionX) | (fromSectionZ ^ toSectionZ)) == 0) {
++ return this.lock(fromX, fromZ);
++ }
++
++ final long[] areaAffected = new long[(toSectionX - fromSectionX + 1) * (toSectionZ - fromSectionZ + 1)];
++ int areaAffectedLen = 0;
++
++ final Node ret = new Node(this, areaAffected, currThread);
++
++ for (long failures = 0L;;) {
++ Node park = null;
++ boolean addedToArea = false;
++ boolean alreadyOwned = false;
++ boolean allOwned = true;
++
++ // try to fast acquire area
++ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) {
++ for (int currX = fromSectionX; currX <= toSectionX; ++currX) {
++ final long coordinate = IntPairUtil.key(currX, currZ);
++
++ final Node prev = this.nodes.putIfAbsent(coordinate, ret);
++
++ if (prev == null) {
++ addedToArea = true;
++ allOwned = false;
++ areaAffected[areaAffectedLen++] = coordinate;
++ continue;
++ }
++
++ if (prev.thread != currThread) {
++ park = prev;
++ alreadyOwned = true;
++ break;
++ }
++ }
++ }
++
++ // check for failure
++ if ((park != null && addedToArea) || (park == null && alreadyOwned && !allOwned)) {
++ // failure to acquire: added and we need to block, or improper lock usage
++ for (int i = 0; i < areaAffectedLen; ++i) {
++ final long key = areaAffected[i];
++
++ if (this.nodes.remove(key) != ret) {
++ throw new IllegalStateException();
++ }
++ }
++
++ areaAffectedLen = 0;
++
++ // since we inserted, we need to drain waiters
++ Thread unpark;
++ while ((unpark = ret.pollOrBlockAdds()) != null) {
++ LockSupport.unpark(unpark);
++ }
++ }
++
++ if (park == null) {
++ if (alreadyOwned && !allOwned) {
++ throw new IllegalStateException("Improper lock usage: Should never acquire intersecting areas");
++ }
++ ret.areaAffectedLen = areaAffectedLen;
++ return ret;
++ }
++
++ // failed
++
++ ++failures;
++
++ if (failures > 128L && park.add(currThread)) {
++ LockSupport.park(park);
++ } else {
++ // high contention, spin wait
++ if (failures < 128L) {
++ for (long i = 0; i < failures; ++i) {
++ Thread.onSpinWait();
++ }
++ failures = failures << 1;
++ } else if (failures < 1_200L) {
++ LockSupport.parkNanos(1_000L);
++ failures = failures + 1L;
++ } else { // scale 0.1ms (100us) per failure
++ Thread.yield();
++ LockSupport.parkNanos(100_000L * failures);
++ failures = failures + 1L;
++ }
++ }
++
++ if (addedToArea) {
++ // try again, so we need to allow adds so that other threads can properly block on us
++ ret.allowAdds();
++ }
++ }
++ }
++
++ public void unlock(final Node node) {
++ if (node.lock != this) {
++ throw new IllegalStateException("Unlock target lock mismatch");
++ }
++
++ final long[] areaAffected = node.areaAffected;
++ final int areaAffectedLen = node.areaAffectedLen;
++
++ if (areaAffectedLen == 0) {
++ // here we are not in the node map, and so do not need to remove from the node map or unblock any waiters
++ return;
++ }
++
++ Objects.checkFromToIndex(0, areaAffectedLen, areaAffected.length);
++
++ // remove from node map; allowing other threads to lock
++ for (int i = 0; i < areaAffectedLen; ++i) {
++ final long coordinate = areaAffected[i];
++ if (this.nodes.remove(coordinate, node) != node) {
++ throw new IllegalStateException();
++ }
++ }
++
++ Thread unpark;
++ while ((unpark = node.pollOrBlockAdds()) != null) {
++ LockSupport.unpark(unpark);
++ }
++ }
++
++ public static final class Node extends MultiThreadedQueue<Thread> {
++
++ private final ReentrantAreaLock lock;
++ private final long[] areaAffected;
++ private int areaAffectedLen;
++ private final Thread thread;
++
++ private Node(final ReentrantAreaLock lock, final long[] areaAffected, final Thread thread) {
++ this.lock = lock;
++ this.areaAffected = areaAffected;
++ this.thread = thread;
++ }
++
++ @Override
++ public String toString() {
++ return "Node{" +
++ "areaAffected=" + IntPairUtil.toString(this.areaAffected, 0, this.areaAffectedLen) +
++ ", thread=" + this.thread +
++ '}';
++ }
++ }
++}
+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..6abee91e0d83c6a172e890bbda304a512cf790a1
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java
+@@ -0,0 +1,1681 @@
++package ca.spottedleaf.concurrentutil.map;
++
++import ca.spottedleaf.concurrentutil.function.BiLong1Function;
++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
++import ca.spottedleaf.concurrentutil.util.HashUtil;
++import ca.spottedleaf.concurrentutil.util.IntegerUtil;
++import ca.spottedleaf.concurrentutil.util.ThrowUtil;
++import ca.spottedleaf.concurrentutil.util.Validate;
++
++import java.lang.invoke.VarHandle;
++import java.util.Arrays;
++import java.util.Iterator;
++import java.util.NoSuchElementException;
++import java.util.PrimitiveIterator;
++import java.util.concurrent.atomic.LongAdder;
++import java.util.function.BiFunction;
++import java.util.function.Consumer;
++import java.util.function.Function;
++import java.util.function.LongConsumer;
++import java.util.function.LongFunction;
++import java.util.function.Predicate;
++
++/**
++ * Concurrent hashtable implementation supporting mapping arbitrary {@code long} values onto non-null {@code Object}
++ * values with support for multiple writer and multiple reader threads.
++ *
++ * <p><h3>Happens-before relationship</h3></p>
++ * <p>
++ * As with {@link java.util.concurrent.ConcurrentMap}, there is a happens-before relationship between actions in one thread
++ * prior to writing to the map and access to the results of those actions in another thread.
++ * </p>
++ *
++ * <p><h3>Atomicity of functional methods</h3></p>
++ * <p>
++ * Functional methods are functions declared in this class which possibly perform a write (remove, replace, or modify)
++ * to an entry in this map as a result of invoking a function on an input parameter. For example, {@link #compute(long, BiLong1Function)},
++ * {@link #merge(long, Object, BiFunction)} and {@link #removeIf(long, Predicate)} are examples of functional methods.
++ * Functional methods will be performed atomically, that is, the input parameter is guaranteed to only be invoked at most
++ * once per function call. The consequence of this behavior however is that a critical lock for a bin entry is held, which
++ * means that if the input parameter invocation makes additional calls to write into this hash table that the result
++ * is undefined and deadlock-prone.
++ * </p>
++ *
++ * @param <V>
++ * @see java.util.concurrent.ConcurrentMap
++ */
++public class ConcurrentLong2ReferenceChainedHashTable<V> {
++
++ protected static final int DEFAULT_CAPACITY = 16;
++ protected static final float DEFAULT_LOAD_FACTOR = 0.75f;
++ protected static final int MAXIMUM_CAPACITY = Integer.MIN_VALUE >>> 1;
++
++ protected final LongAdder size = new LongAdder();
++ protected final float loadFactor;
++
++ protected volatile TableEntry<V>[] table;
++
++ protected static final int THRESHOLD_NO_RESIZE = -1;
++ protected static final int THRESHOLD_RESIZING = -2;
++ protected volatile int threshold;
++ protected static final VarHandle THRESHOLD_HANDLE = ConcurrentUtil.getVarHandle(ConcurrentLong2ReferenceChainedHashTable.class, "threshold", int.class);
++
++ protected final int getThresholdAcquire() {
++ return (int)THRESHOLD_HANDLE.getAcquire(this);
++ }
++
++ protected final int getThresholdVolatile() {
++ return (int)THRESHOLD_HANDLE.getVolatile(this);
++ }
++
++ protected final void setThresholdPlain(final int threshold) {
++ THRESHOLD_HANDLE.set(this, threshold);
++ }
++
++ protected final void setThresholdRelease(final int threshold) {
++ THRESHOLD_HANDLE.setRelease(this, threshold);
++ }
++
++ protected final void setThresholdVolatile(final int threshold) {
++ THRESHOLD_HANDLE.setVolatile(this, threshold);
++ }
++
++ protected final int compareExchangeThresholdVolatile(final int expect, final int update) {
++ return (int)THRESHOLD_HANDLE.compareAndExchange(this, expect, update);
++ }
++
++ public ConcurrentLong2ReferenceChainedHashTable() {
++ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
++ }
++
++ protected static int getTargetThreshold(final int capacity, final float loadFactor) {
++ final double ret = (double)capacity * (double)loadFactor;
++ if (Double.isInfinite(ret) || ret >= ((double)Integer.MAX_VALUE)) {
++ return THRESHOLD_NO_RESIZE;
++ }
++
++ return (int)Math.ceil(ret);
++ }
++
++ protected static int getCapacityFor(final int capacity) {
++ if (capacity <= 0) {
++ throw new IllegalArgumentException("Invalid capacity: " + capacity);
++ }
++ if (capacity >= MAXIMUM_CAPACITY) {
++ return MAXIMUM_CAPACITY;
++ }
++ return IntegerUtil.roundCeilLog2(capacity);
++ }
++
++ protected ConcurrentLong2ReferenceChainedHashTable(final int capacity, final float loadFactor) {
++ final int tableSize = getCapacityFor(capacity);
++
++ if (loadFactor <= 0.0 || !Float.isFinite(loadFactor)) {
++ throw new IllegalArgumentException("Invalid load factor: " + loadFactor);
++ }
++
++ if (tableSize == MAXIMUM_CAPACITY) {
++ this.setThresholdPlain(THRESHOLD_NO_RESIZE);
++ } else {
++ this.setThresholdPlain(getTargetThreshold(tableSize, loadFactor));
++ }
++
++ this.loadFactor = loadFactor;
++ // noinspection unchecked
++ this.table = (TableEntry<V>[])new TableEntry[tableSize];
++ }
++
++ public static <V> ConcurrentLong2ReferenceChainedHashTable<V> createWithCapacity(final int capacity) {
++ return createWithCapacity(capacity, DEFAULT_LOAD_FACTOR);
++ }
++
++ public static <V> ConcurrentLong2ReferenceChainedHashTable<V> createWithCapacity(final int capacity, final float loadFactor) {
++ return new ConcurrentLong2ReferenceChainedHashTable<>(capacity, loadFactor);
++ }
++
++ public static <V> ConcurrentLong2ReferenceChainedHashTable<V> createWithExpected(final int expected) {
++ return createWithExpected(expected, DEFAULT_LOAD_FACTOR);
++ }
++
++ public static <V> ConcurrentLong2ReferenceChainedHashTable<V> createWithExpected(final int expected, final float loadFactor) {
++ final int capacity = (int)Math.ceil((double)expected / (double)loadFactor);
++
++ return createWithCapacity(capacity, loadFactor);
++ }
++
++ /** must be deterministic given a key */
++ protected static int getHash(final long key) {
++ return (int)HashUtil.mix(key);
++ }
++
++ /**
++ * Returns the load factor associated with this map.
++ */
++ public final float getLoadFactor() {
++ return this.loadFactor;
++ }
++
++ protected static <V> TableEntry<V> getAtIndexVolatile(final TableEntry<V>[] table, final int index) {
++ //noinspection unchecked
++ return (TableEntry<V>)TableEntry.TABLE_ENTRY_ARRAY_HANDLE.getVolatile(table, index);
++ }
++
++ protected static <V> void setAtIndexRelease(final TableEntry<V>[] table, final int index, final TableEntry<V> value) {
++ TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setRelease(table, index, value);
++ }
++
++ protected static <V> void setAtIndexVolatile(final TableEntry<V>[] table, final int index, final TableEntry<V> value) {
++ TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setVolatile(table, index, value);
++ }
++
++ protected static <V> TableEntry<V> compareAndExchangeAtIndexVolatile(final TableEntry<V>[] table, final int index,
++ final TableEntry<V> expect, final TableEntry<V> update) {
++ //noinspection unchecked
++ return (TableEntry<V>)TableEntry.TABLE_ENTRY_ARRAY_HANDLE.compareAndExchange(table, index, expect, update);
++ }
++
++ /**
++ * Returns the possible node associated with the key, or {@code null} if there is no such node. The node
++ * returned may have a {@code null} {@link TableEntry#value}, in which case the node is a placeholder for
++ * a compute/computeIfAbsent call. The placeholder node should not be considered mapped in order to preserve
++ * happens-before relationships between writes and reads in the map.
++ */
++ protected final TableEntry<V> getNode(final long key) {
++ final int hash = getHash(key);
++
++ TableEntry<V>[] table = this.table;
++ for (;;) {
++ TableEntry<V> node = getAtIndexVolatile(table, hash & (table.length - 1));
++
++ if (node == null) {
++ // node == null
++ return node;
++ }
++
++ if (node.resize) {
++ table = (TableEntry<V>[])node.getValuePlain();
++ continue;
++ }
++
++ for (; node != null; node = node.getNextVolatile()) {
++ if (node.key == key) {
++ return node;
++ }
++ }
++
++ // node == null
++ return node;
++ }
++ }
++
++ /**
++ * Returns the currently mapped value associated with the specified key, or {@code null} if there is none.
++ *
++ * @param key Specified key
++ */
++ public V get(final long key) {
++ final TableEntry<V> node = this.getNode(key);
++ return node == null ? null : node.getValueVolatile();
++ }
++
++ /**
++ * Returns the currently mapped value associated with the specified key, or the specified default value if there is none.
++ *
++ * @param key Specified key
++ * @param defaultValue Specified default value
++ */
++ public V getOrDefault(final long key, final V defaultValue) {
++ final TableEntry<V> node = this.getNode(key);
++ if (node == null) {
++ return defaultValue;
++ }
++
++ final V ret = node.getValueVolatile();
++ if (ret == null) {
++ // ret == null for nodes pre-allocated to compute() and friends
++ return defaultValue;
++ }
++
++ return ret;
++ }
++
++ /**
++ * Returns whether the specified key is mapped to some value.
++ * @param key Specified key
++ */
++ public boolean containsKey(final long key) {
++ // cannot use getNode, as the node may be a placeholder for compute()
++ return this.get(key) != null;
++ }
++
++ /**
++ * Returns whether the specified value has a key mapped to it.
++ * @param value Specified value
++ * @throws NullPointerException If value is null
++ */
++ public boolean containsValue(final V value) {
++ Validate.notNull(value, "Value cannot be null");
++
++ final NodeIterator<V> iterator = new NodeIterator<>(this.table);
++
++ TableEntry<V> node;
++ while ((node = iterator.findNext()) != null) {
++ // need to use acquire here to ensure the happens-before relationship
++ if (node.getValueAcquire() == value) {
++ return true;
++ }
++ }
++
++ return false;
++ }
++
++ /**
++ * Returns the number of mappings in this map.
++ */
++ public int size() {
++ final long ret = this.size.sum();
++
++ if (ret <= 0L) {
++ return 0;
++ }
++ if (ret >= (long)Integer.MAX_VALUE) {
++ return Integer.MAX_VALUE;
++ }
++
++ return (int)ret;
++ }
++
++ /**
++ * Returns whether this map has no mappings.
++ */
++ public boolean isEmpty() {
++ return this.size.sum() <= 0L;
++ }
++
++ /**
++ * Adds count to size and checks threshold for resizing
++ */
++ protected final void addSize(final long count) {
++ this.size.add(count);
++
++ final int threshold = this.getThresholdAcquire();
++
++ if (threshold < 0L) {
++ // resizing or no resizing allowed, in either cases we do not need to do anything
++ return;
++ }
++
++ final long sum = this.size.sum();
++
++ if (sum < (long)threshold) {
++ return;
++ }
++
++ if (threshold != this.compareExchangeThresholdVolatile(threshold, THRESHOLD_RESIZING)) {
++ // some other thread resized
++ return;
++ }
++
++ // create new table
++ this.resize(sum);
++ }
++
++ /**
++ * Resizes table, only invoke for the thread which has successfully updated threshold to {@link #THRESHOLD_RESIZING}
++ * @param sum Estimate of current mapping count, must be >= old threshold
++ */
++ private void resize(final long sum) {
++ int capacity;
++
++ // add 1.0, as sum may equal threshold (in which case, sum / loadFactor = current capacity)
++ // adding 1.0 should at least raise the size by a factor of two due to usage of roundCeilLog2
++ final double targetD = ((double)sum / (double)this.loadFactor) + 1.0;
++ if (targetD >= (double)MAXIMUM_CAPACITY) {
++ capacity = MAXIMUM_CAPACITY;
++ } else {
++ capacity = (int)Math.ceil(targetD);
++ capacity = IntegerUtil.roundCeilLog2(capacity);
++ if (capacity > MAXIMUM_CAPACITY) {
++ capacity = MAXIMUM_CAPACITY;
++ }
++ }
++
++ // create new table data
++
++ final TableEntry<V>[] newTable = new TableEntry[capacity];
++ // noinspection unchecked
++ final TableEntry<V> resizeNode = new TableEntry<>(0L, (V)newTable, true);
++
++ // transfer nodes from old table
++
++ // does not need to be volatile read, just plain
++ final TableEntry<V>[] oldTable = this.table;
++
++ // when resizing, the old entries at bin i (where i = hash % oldTable.length) are assigned to
++ // bin k in the new table (where k = hash % newTable.length)
++ // since both table lengths are powers of two (specifically, newTable is a multiple of oldTable),
++ // the possible number of locations in the new table to assign any given i is newTable.length/oldTable.length
++
++ // we can build the new linked nodes for the new table by using a work array sized to newTable.length/oldTable.length
++ // which holds the _last_ entry in the chain per bin
++
++ final int capOldShift = IntegerUtil.floorLog2(oldTable.length);
++ final int capDiffShift = IntegerUtil.floorLog2(capacity) - capOldShift;
++
++ if (capDiffShift == 0) {
++ throw new IllegalStateException("Resizing to same size");
++ }
++
++ final TableEntry<V>[] work = new TableEntry[1 << capDiffShift]; // typically, capDiffShift = 1
++
++ for (int i = 0, len = oldTable.length; i < len; ++i) {
++ TableEntry<V> binNode = getAtIndexVolatile(oldTable, i);
++
++ for (;;) {
++ if (binNode == null) {
++ // just need to replace the bin node, do not need to move anything
++ if (null == (binNode = compareAndExchangeAtIndexVolatile(oldTable, i, null, resizeNode))) {
++ break;
++ } // else: binNode != null, fall through
++ }
++
++ // need write lock to block other writers
++ synchronized (binNode) {
++ if (binNode != (binNode = getAtIndexVolatile(oldTable, i))) {
++ continue;
++ }
++
++ // an important detail of resizing is that we do not need to be concerned with synchronisation on
++ // writes to the new table, as no access to any nodes on bin i on oldTable will occur until a thread
++ // sees the resizeNode
++ // specifically, as long as the resizeNode is release written there are no cases where another thread
++ // will see our writes to the new table
++
++ TableEntry<V> next = binNode.getNextPlain();
++
++ if (next == null) {
++ // simple case: do not use work array
++
++ // do not need to create new node, readers only need to see the state of the map at the
++ // beginning of a call, so any additions onto _next_ don't really matter
++ // additionally, the old node is replaced so that writers automatically forward to the new table,
++ // which resolves any issues
++ newTable[getHash(binNode.key) & (capacity - 1)] = binNode;
++ } else {
++ // reset for next usage
++ Arrays.fill(work, null);
++
++ for (TableEntry<V> curr = binNode; curr != null; curr = curr.getNextPlain()) {
++ final int newTableIdx = getHash(curr.key) & (capacity - 1);
++ final int workIdx = newTableIdx >>> capOldShift;
++
++ final TableEntry<V> replace = new TableEntry<>(curr.key, curr.getValuePlain());
++
++ final TableEntry<V> workNode = work[workIdx];
++ work[workIdx] = replace;
++
++ if (workNode == null) {
++ newTable[newTableIdx] = replace;
++ continue;
++ } else {
++ workNode.setNextPlain(replace);
++ continue;
++ }
++ }
++ }
++
++ setAtIndexRelease(oldTable, i, resizeNode);
++ break;
++ }
++ }
++ }
++
++ // calculate new threshold
++ final int newThreshold;
++ if (capacity == MAXIMUM_CAPACITY) {
++ newThreshold = THRESHOLD_NO_RESIZE;
++ } else {
++ newThreshold = getTargetThreshold(capacity, loadFactor);
++ }
++
++ this.table = newTable;
++ // finish resize operation by releasing hold on threshold
++ this.setThresholdVolatile(newThreshold);
++ }
++
++ /**
++ * Subtracts count from size
++ */
++ protected final void subSize(final long count) {
++ this.size.add(-count);
++ }
++
++ /**
++ * Atomically updates the value associated with {@code key} to {@code value}, or inserts a new mapping with {@code key}
++ * mapped to {@code value}.
++ * @param key Specified key
++ * @param value Specified value
++ * @throws NullPointerException If value is null
++ * @return Old value previously associated with key, or {@code null} if none.
++ */
++ public V put(final long key, final V value) {
++ Validate.notNull(value, "Value may not be null");
++
++ final int hash = getHash(key);
++
++ TableEntry<V>[] table = this.table;
++ table_loop:
++ for (;;) {
++ final int index = hash & (table.length - 1);
++
++ TableEntry<V> node = getAtIndexVolatile(table, index);
++ node_loop:
++ for (;;) {
++ if (node == null) {
++ if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, new TableEntry<>(key, value)))) {
++ // successfully inserted
++ this.addSize(1L);
++ return null;
++ } // else: node != null, fall through
++ }
++
++ if (node.resize) {
++ table = (TableEntry<V>[])node.getValuePlain();
++ continue table_loop;
++ }
++
++ synchronized (node) {
++ if (node != (node = getAtIndexVolatile(table, index))) {
++ continue node_loop;
++ }
++ // plain reads are fine during synchronised access, as we are the only writer
++ TableEntry<V> prev = null;
++ for (; node != null; prev = node, node = node.getNextPlain()) {
++ if (node.key == key) {
++ final V ret = node.getValuePlain();
++ node.setValueVolatile(value);
++ return ret;
++ }
++ }
++
++ // volatile ordering ensured by addSize(), but we need release here
++ // to ensure proper ordering with reads and other writes
++ prev.setNextRelease(new TableEntry<>(key, value));
++ }
++
++ this.addSize(1L);
++ return null;
++ }
++ }
++ }
++
++ /**
++ * Atomically inserts a new mapping with {@code key} mapped to {@code value} if and only if {@code key} is not
++ * currently mapped to some value.
++ * @param key Specified key
++ * @param value Specified value
++ * @throws NullPointerException If value is null
++ * @return Value currently associated with key, or {@code null} if none and {@code value} was associated.
++ */
++ public V putIfAbsent(final long key, final V value) {
++ Validate.notNull(value, "Value may not be null");
++
++ final int hash = getHash(key);
++
++ TableEntry<V>[] table = this.table;
++ table_loop:
++ for (;;) {
++ final int index = hash & (table.length - 1);
++
++ TableEntry<V> node = getAtIndexVolatile(table, index);
++ node_loop:
++ for (;;) {
++ if (node == null) {
++ if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, new TableEntry<>(key, value)))) {
++ // successfully inserted
++ this.addSize(1L);
++ return null;
++ } // else: node != null, fall through
++ }
++
++ if (node.resize) {
++ table = (TableEntry<V>[])node.getValuePlain();
++ continue table_loop;
++ }
++
++ // optimise ifAbsent calls: check if first node is key before attempting lock acquire
++ if (node.key == key) {
++ final V ret = node.getValueVolatile();
++ if (ret != null) {
++ return ret;
++ } // else: fall back to lock to read the node
++ }
++
++ synchronized (node) {
++ if (node != (node = getAtIndexVolatile(table, index))) {
++ continue node_loop;
++ }
++ // plain reads are fine during synchronised access, as we are the only writer
++ TableEntry<V> prev = null;
++ for (; node != null; prev = node, node = node.getNextPlain()) {
++ if (node.key == key) {
++ return node.getValuePlain();
++ }
++ }
++
++ // volatile ordering ensured by addSize(), but we need release here
++ // to ensure proper ordering with reads and other writes
++ prev.setNextRelease(new TableEntry<>(key, value));
++ }
++
++ this.addSize(1L);
++ return null;
++ }
++ }
++ }
++
++ /**
++ * Atomically updates the value associated with {@code key} to {@code value}, or does nothing if {@code key} is not
++ * associated with a value.
++ * @param key Specified key
++ * @param value Specified value
++ * @throws NullPointerException If value is null
++ * @return Old value previously associated with key, or {@code null} if none.
++ */
++ public V replace(final long key, final V value) {
++ Validate.notNull(value, "Value may not be null");
++
++ final int hash = getHash(key);
++
++ TableEntry<V>[] table = this.table;
++ table_loop:
++ for (;;) {
++ final int index = hash & (table.length - 1);
++
++ TableEntry<V> node = getAtIndexVolatile(table, index);
++ node_loop:
++ for (;;) {
++ if (node == null) {
++ return null;
++ }
++
++ if (node.resize) {
++ table = (TableEntry<V>[])node.getValuePlain();
++ continue table_loop;
++ }
++
++ synchronized (node) {
++ if (node != (node = getAtIndexVolatile(table, index))) {
++ continue node_loop;
++ }
++
++ // plain reads are fine during synchronised access, as we are the only writer
++ for (; node != null; node = node.getNextPlain()) {
++ if (node.key == key) {
++ final V ret = node.getValuePlain();
++ node.setValueVolatile(value);
++ return ret;
++ }
++ }
++ }
++
++ return null;
++ }
++ }
++ }
++
++ /**
++ * Atomically updates the value associated with {@code key} to {@code update} if the currently associated
++ * value is reference equal to {@code expect}, otherwise does nothing.
++ * @param key Specified key
++ * @param expect Expected value to check current mapped value with
++ * @param update Update value to replace mapped value with
++ * @throws NullPointerException If value is null
++ * @return If the currently mapped value is not reference equal to {@code expect}, then returns the currently mapped
++ * value. If the key is not mapped to any value, then returns {@code null}. If neither of the two cases are
++ * true, then returns {@code expect}.
++ */
++ public V replace(final long key, final V expect, final V update) {
++ Validate.notNull(expect, "Expect may not be null");
++ Validate.notNull(update, "Update may not be null");
++
++ final int hash = getHash(key);
++
++ TableEntry<V>[] table = this.table;
++ table_loop:
++ for (;;) {
++ final int index = hash & (table.length - 1);
++
++ TableEntry<V> node = getAtIndexVolatile(table, index);
++ node_loop:
++ for (;;) {
++ if (node == null) {
++ return null;
++ }
++
++ if (node.resize) {
++ table = (TableEntry<V>[])node.getValuePlain();
++ continue table_loop;
++ }
++
++ synchronized (node) {
++ if (node != (node = getAtIndexVolatile(table, index))) {
++ continue node_loop;
++ }
++
++ // plain reads are fine during synchronised access, as we are the only writer
++ for (; node != null; node = node.getNextPlain()) {
++ if (node.key == key) {
++ final V ret = node.getValuePlain();
++
++ if (ret != expect) {
++ return ret;
++ }
++
++ node.setValueVolatile(update);
++ return ret;
++ }
++ }
++ }
++
++ return null;
++ }
++ }
++ }
++
++ /**
++ * Atomically removes the mapping for the specified key and returns the value it was associated with. If the key
++ * is not mapped to a value, then does nothing and returns {@code null}.
++ * @param key Specified key
++ * @return Old value previously associated with key, or {@code null} if none.
++ */
++ public V remove(final long key) {
++ final int hash = getHash(key);
++
++ TableEntry<V>[] table = this.table;
++ table_loop:
++ for (;;) {
++ final int index = hash & (table.length - 1);
++
++ TableEntry<V> node = getAtIndexVolatile(table, index);
++ node_loop:
++ for (;;) {
++ if (node == null) {
++ return null;
++ }
++
++ if (node.resize) {
++ table = (TableEntry<V>[])node.getValuePlain();
++ continue table_loop;
++ }
++
++ boolean removed = false;
++ V ret = null;
++
++ synchronized (node) {
++ if (node != (node = getAtIndexVolatile(table, index))) {
++ continue node_loop;
++ }
++
++ TableEntry<V> prev = null;
++
++ // plain reads are fine during synchronised access, as we are the only writer
++ for (; node != null; prev = node, node = node.getNextPlain()) {
++ if (node.key == key) {
++ ret = node.getValuePlain();
++ removed = true;
++
++ // volatile ordering ensured by addSize(), but we need release here
++ // to ensure proper ordering with reads and other writes
++ if (prev == null) {
++ setAtIndexRelease(table, index, node.getNextPlain());
++ } else {
++ prev.setNextRelease(node.getNextPlain());
++ }
++
++ break;
++ }
++ }
++ }
++
++ if (removed) {
++ this.subSize(1L);
++ }
++
++ return ret;
++ }
++ }
++ }
++
++ /**
++ * Atomically removes the mapping for the specified key if it is mapped to {@code expect} and returns {@code expect}. If the key
++ * is not mapped to a value, then does nothing and returns {@code null}. If the key is mapped to a value that is not reference
++ * equal to {@code expect}, then returns that value.
++ * @param key Specified key
++ * @param expect Specified expected value
++ * @return The specified expected value if the key was mapped to {@code expect}. If
++ * the key is not mapped to any value, then returns {@code null}. If neither of those cases are true,
++ * then returns the current (non-null) mapped value for key.
++ */
++ public V remove(final long key, final V expect) {
++ final int hash = getHash(key);
++
++ TableEntry<V>[] table = this.table;
++ table_loop:
++ for (;;) {
++ final int index = hash & (table.length - 1);
++
++ TableEntry<V> node = getAtIndexVolatile(table, index);
++ node_loop:
++ for (;;) {
++ if (node == null) {
++ return null;
++ }
++
++ if (node.resize) {
++ table = (TableEntry<V>[])node.getValuePlain();
++ continue table_loop;
++ }
++
++ boolean removed = false;
++ V ret = null;
++
++ synchronized (node) {
++ if (node != (node = getAtIndexVolatile(table, index))) {
++ continue node_loop;
++ }
++
++ TableEntry<V> prev = null;
++
++ // plain reads are fine during synchronised access, as we are the only writer
++ for (; node != null; prev = node, node = node.getNextPlain()) {
++ if (node.key == key) {
++ ret = node.getValuePlain();
++ if (ret == expect) {
++ removed = true;
++
++ // volatile ordering ensured by addSize(), but we need release here
++ // to ensure proper ordering with reads and other writes
++ if (prev == null) {
++ setAtIndexRelease(table, index, node.getNextPlain());
++ } else {
++ prev.setNextRelease(node.getNextPlain());
++ }
++ }
++ break;
++ }
++ }
++ }
++
++ if (removed) {
++ this.subSize(1L);
++ }
++
++ return ret;
++ }
++ }
++ }
++
++ /**
++ * Atomically removes the mapping for the specified key the predicate returns true for its currently mapped value. If the key
++ * is not mapped to a value, then does nothing and returns {@code null}.
++ *
++ * <p>
++ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}.
++ * </p>
++ *
++ * @param key Specified key
++ * @param predicate Specified predicate
++ * @throws NullPointerException If predicate is null
++ * @return The specified expected value if the key was mapped to {@code expect}. If
++ * the key is not mapped to any value, then returns {@code null}. If neither of those cases are true,
++ * then returns the current (non-null) mapped value for key.
++ */
++ public V removeIf(final long key, final Predicate<? super V> predicate) {
++ Validate.notNull(predicate, "Predicate may not be null");
++
++ final int hash = getHash(key);
++
++ TableEntry<V>[] table = this.table;
++ table_loop:
++ for (;;) {
++ final int index = hash & (table.length - 1);
++
++ TableEntry<V> node = getAtIndexVolatile(table, index);
++ node_loop:
++ for (;;) {
++ if (node == null) {
++ return null;
++ }
++
++ if (node.resize) {
++ table = (TableEntry<V>[])node.getValuePlain();
++ continue table_loop;
++ }
++
++ boolean removed = false;
++ V ret = null;
++
++ synchronized (node) {
++ if (node != (node = getAtIndexVolatile(table, index))) {
++ continue node_loop;
++ }
++
++ TableEntry<V> prev = null;
++
++ // plain reads are fine during synchronised access, as we are the only writer
++ for (; node != null; prev = node, node = node.getNextPlain()) {
++ if (node.key == key) {
++ ret = node.getValuePlain();
++ if (predicate.test(ret)) {
++ removed = true;
++
++ // volatile ordering ensured by addSize(), but we need release here
++ // to ensure proper ordering with reads and other writes
++ if (prev == null) {
++ setAtIndexRelease(table, index, node.getNextPlain());
++ } else {
++ prev.setNextRelease(node.getNextPlain());
++ }
++ }
++ break;
++ }
++ }
++ }
++
++ if (removed) {
++ this.subSize(1L);
++ }
++
++ return ret;
++ }
++ }
++ }
++
++ /**
++ * See {@link java.util.concurrent.ConcurrentMap#compute(Object, BiFunction)}
++ * <p>
++ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}.
++ * </p>
++ */
++ public V compute(final long key, final BiLong1Function<? super V, ? extends V> function) {
++ final int hash = getHash(key);
++
++ TableEntry<V>[] table = this.table;
++ table_loop:
++ for (;;) {
++ final int index = hash & (table.length - 1);
++
++ TableEntry<V> node = getAtIndexVolatile(table, index);
++ node_loop:
++ for (;;) {
++ V ret = null;
++ if (node == null) {
++ final TableEntry<V> insert = new TableEntry<>(key, null);
++
++ boolean added = false;
++
++ synchronized (insert) {
++ if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, insert))) {
++ try {
++ ret = function.apply(key, null);
++ } catch (final Throwable throwable) {
++ setAtIndexVolatile(table, index, null);
++ ThrowUtil.throwUnchecked(throwable);
++ // unreachable
++ return null;
++ }
++
++ if (ret == null) {
++ setAtIndexVolatile(table, index, null);
++ return ret;
++ } else {
++ // volatile ordering ensured by addSize(), but we need release here
++ // to ensure proper ordering with reads and other writes
++ insert.setValueRelease(ret);
++ added = true;
++ }
++ } // else: node != null, fall through
++ }
++
++ if (added) {
++ this.addSize(1L);
++ return ret;
++ }
++ }
++
++ if (node.resize) {
++ table = (TableEntry<V>[])node.getValuePlain();
++ continue table_loop;
++ }
++
++ boolean removed = false;
++ boolean added = false;
++
++ synchronized (node) {
++ if (node != (node = getAtIndexVolatile(table, index))) {
++ continue node_loop;
++ }
++ // plain reads are fine during synchronised access, as we are the only writer
++ TableEntry<V> prev = null;
++ for (; node != null; prev = node, node = node.getNextPlain()) {
++ if (node.key == key) {
++ final V old = node.getValuePlain();
++
++ final V computed = function.apply(key, old);
++
++ if (computed != null) {
++ node.setValueVolatile(computed);
++ return computed;
++ }
++
++ // volatile ordering ensured by addSize(), but we need release here
++ // to ensure proper ordering with reads and other writes
++ if (prev == null) {
++ setAtIndexRelease(table, index, node.getNextPlain());
++ } else {
++ prev.setNextRelease(node.getNextPlain());
++ }
++
++ removed = true;
++ break;
++ }
++ }
++
++ if (!removed) {
++ final V computed = function.apply(key, null);
++ if (computed != null) {
++ // volatile ordering ensured by addSize(), but we need release here
++ // to ensure proper ordering with reads and other writes
++ prev.setNextRelease(new TableEntry<>(key, computed));
++ ret = computed;
++ added = true;
++ }
++ }
++ }
++
++ if (removed) {
++ this.subSize(1L);
++ }
++ if (added) {
++ this.addSize(1L);
++ }
++
++ return ret;
++ }
++ }
++ }
++
++ /**
++ * See {@link java.util.concurrent.ConcurrentMap#computeIfAbsent(Object, Function)}
++ * <p>
++ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}.
++ * </p>
++ */
++ public V computeIfAbsent(final long key, final LongFunction<? extends V> function) {
++ final int hash = getHash(key);
++
++ TableEntry<V>[] table = this.table;
++ table_loop:
++ for (;;) {
++ final int index = hash & (table.length - 1);
++
++ TableEntry<V> node = getAtIndexVolatile(table, index);
++ node_loop:
++ for (;;) {
++ V ret = null;
++ if (node == null) {
++ final TableEntry<V> insert = new TableEntry<>(key, null);
++
++ boolean added = false;
++
++ synchronized (insert) {
++ if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, insert))) {
++ try {
++ ret = function.apply(key);
++ } catch (final Throwable throwable) {
++ setAtIndexVolatile(table, index, null);
++ ThrowUtil.throwUnchecked(throwable);
++ // unreachable
++ return null;
++ }
++
++ if (ret == null) {
++ setAtIndexVolatile(table, index, null);
++ return null;
++ } else {
++ // volatile ordering ensured by addSize(), but we need release here
++ // to ensure proper ordering with reads and other writes
++ insert.setValueRelease(ret);
++ added = true;
++ }
++ } // else: node != null, fall through
++ }
++
++ if (added) {
++ this.addSize(1L);
++ return ret;
++ }
++ }
++
++ if (node.resize) {
++ table = (TableEntry<V>[])node.getValuePlain();
++ continue table_loop;
++ }
++
++ // optimise ifAbsent calls: check if first node is key before attempting lock acquire
++ if (node.key == key) {
++ ret = node.getValueVolatile();
++ if (ret != null) {
++ return ret;
++ } // else: fall back to lock to read the node
++ }
++
++ boolean added = false;
++
++ synchronized (node) {
++ if (node != (node = getAtIndexVolatile(table, index))) {
++ continue node_loop;
++ }
++ // plain reads are fine during synchronised access, as we are the only writer
++ TableEntry<V> prev = null;
++ for (; node != null; prev = node, node = node.getNextPlain()) {
++ if (node.key == key) {
++ ret = node.getValuePlain();
++ return ret;
++ }
++ }
++
++ final V computed = function.apply(key);
++ if (computed != null) {
++ // volatile ordering ensured by addSize(), but we need release here
++ // to ensure proper ordering with reads and other writes
++ prev.setNextRelease(new TableEntry<>(key, computed));
++ ret = computed;
++ added = true;
++ }
++ }
++
++ if (added) {
++ this.addSize(1L);
++ }
++
++ return ret;
++ }
++ }
++ }
++
++ /**
++ * See {@link java.util.concurrent.ConcurrentMap#computeIfPresent(Object, BiFunction)}
++ * <p>
++ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}.
++ * </p>
++ */
++ public V computeIfPresent(final long key, final BiLong1Function<? super V, ? extends V> function) {
++ final int hash = getHash(key);
++
++ TableEntry<V>[] table = this.table;
++ table_loop:
++ for (;;) {
++ final int index = hash & (table.length - 1);
++
++ TableEntry<V> node = getAtIndexVolatile(table, index);
++ node_loop:
++ for (;;) {
++ if (node == null) {
++ return null;
++ }
++
++ if (node.resize) {
++ table = (TableEntry<V>[])node.getValuePlain();
++ continue table_loop;
++ }
++
++ boolean removed = false;
++
++ synchronized (node) {
++ if (node != (node = getAtIndexVolatile(table, index))) {
++ continue node_loop;
++ }
++ // plain reads are fine during synchronised access, as we are the only writer
++ TableEntry<V> prev = null;
++ for (; node != null; prev = node, node = node.getNextPlain()) {
++ if (node.key == key) {
++ final V old = node.getValuePlain();
++
++ final V computed = function.apply(key, old);
++
++ if (computed != null) {
++ node.setValueVolatile(computed);
++ return computed;
++ }
++
++ // volatile ordering ensured by addSize(), but we need release here
++ // to ensure proper ordering with reads and other writes
++ if (prev == null) {
++ setAtIndexRelease(table, index, node.getNextPlain());
++ } else {
++ prev.setNextRelease(node.getNextPlain());
++ }
++
++ removed = true;
++ break;
++ }
++ }
++ }
++
++ if (removed) {
++ this.subSize(1L);
++ }
++
++ return null;
++ }
++ }
++ }
++
++ /**
++ * See {@link java.util.concurrent.ConcurrentMap#merge(Object, Object, BiFunction)}
++ * <p>
++ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}.
++ * </p>
++ */
++ public V merge(final long key, final V def, final BiFunction<? super V, ? super V, ? extends V> function) {
++ Validate.notNull(def, "Default value may not be null");
++
++ final int hash = getHash(key);
++
++ TableEntry<V>[] table = this.table;
++ table_loop:
++ for (;;) {
++ final int index = hash & (table.length - 1);
++
++ TableEntry<V> node = getAtIndexVolatile(table, index);
++ node_loop:
++ for (;;) {
++ if (node == null) {
++ if (null == (node = compareAndExchangeAtIndexVolatile(table, index, null, new TableEntry<>(key, def)))) {
++ // successfully inserted
++ this.addSize(1L);
++ return def;
++ } // else: node != null, fall through
++ }
++
++ if (node.resize) {
++ table = (TableEntry<V>[])node.getValuePlain();
++ continue table_loop;
++ }
++
++ boolean removed = false;
++ boolean added = false;
++ V ret = null;
++
++ synchronized (node) {
++ if (node != (node = getAtIndexVolatile(table, index))) {
++ continue node_loop;
++ }
++ // plain reads are fine during synchronised access, as we are the only writer
++ TableEntry<V> prev = null;
++ for (; node != null; prev = node, node = node.getNextPlain()) {
++ if (node.key == key) {
++ final V old = node.getValuePlain();
++
++ final V computed = function.apply(old, def);
++
++ if (computed != null) {
++ node.setValueVolatile(computed);
++ return computed;
++ }
++
++ // volatile ordering ensured by addSize(), but we need release here
++ // to ensure proper ordering with reads and other writes
++ if (prev == null) {
++ setAtIndexRelease(table, index, node.getNextPlain());
++ } else {
++ prev.setNextRelease(node.getNextPlain());
++ }
++
++ removed = true;
++ break;
++ }
++ }
++
++ if (!removed) {
++ // volatile ordering ensured by addSize(), but we need release here
++ // to ensure proper ordering with reads and other writes
++ prev.setNextRelease(new TableEntry<>(key, def));
++ ret = def;
++ added = true;
++ }
++ }
++
++ if (removed) {
++ this.subSize(1L);
++ }
++ if (added) {
++ this.addSize(1L);
++ }
++
++ return ret;
++ }
++ }
++ }
++
++ /**
++ * Removes at least all entries currently mapped at the beginning of this call. May not remove entries added during
++ * this call. As a result, only if this map is not modified during the call, that all entries will be removed by
++ * the end of the call.
++ *
++ * <p>
++ * This function is not atomic.
++ * </p>
++ */
++ public void clear() {
++ // it is possible to optimise this to directly interact with the table,
++ // but we do need to be careful when interacting with resized tables,
++ // and the NodeIterator already does this logic
++ final NodeIterator<V> nodeIterator = new NodeIterator<>(this.table);
++
++ TableEntry<V> node;
++ while ((node = nodeIterator.findNext()) != null) {
++ this.remove(node.key);
++ }
++ }
++
++ /**
++ * Returns an iterator over the entries in this map. The iterator is only guaranteed to see entries that were
++ * added before the beginning of this call, but it may see entries added during.
++ */
++ public Iterator<TableEntry<V>> entryIterator() {
++ return new EntryIterator<>(this);
++ }
++
++ /**
++ * 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.
++ */
++ public PrimitiveIterator.OfLong keyIterator() {
++ return new KeyIterator<>(this);
++ }
++
++ /**
++ * Returns an iterator over the values in this map. The iterator is only guaranteed to see values that were
++ * added before the beginning of this call, but it may see values added during.
++ */
++ public Iterator<V> valueIterator() {
++ return new ValueIterator<>(this);
++ }
++
++ protected static final class EntryIterator<V> extends BaseIteratorImpl<V, TableEntry<V>> {
++
++ protected EntryIterator(final ConcurrentLong2ReferenceChainedHashTable<V> map) {
++ super(map);
++ }
++
++ @Override
++ public TableEntry<V> next() throws NoSuchElementException {
++ return this.nextNode();
++ }
++
++ @Override
++ public void forEachRemaining(final Consumer<? super TableEntry<V>> action) {
++ Validate.notNull(action, "Action may not be null");
++ while (this.hasNext()) {
++ action.accept(this.next());
++ }
++ }
++ }
++
++ protected static final class KeyIterator<V> extends BaseIteratorImpl<V, Long> implements PrimitiveIterator.OfLong {
++
++ protected KeyIterator(final ConcurrentLong2ReferenceChainedHashTable<V> map) {
++ super(map);
++ }
++
++ @Override
++ public Long next() throws NoSuchElementException {
++ return Long.valueOf(this.nextNode().key);
++ }
++
++ @Override
++ public long nextLong() {
++ return this.nextNode().key;
++ }
++
++ @Override
++ public void forEachRemaining(final Consumer<? super Long> action) {
++ Validate.notNull(action, "Action may not be null");
++
++ if (action instanceof LongConsumer longConsumer) {
++ this.forEachRemaining(longConsumer);
++ return;
++ }
++
++ while (this.hasNext()) {
++ action.accept(this.next());
++ }
++ }
++
++ @Override
++ public void forEachRemaining(final LongConsumer action) {
++ Validate.notNull(action, "Action may not be null");
++ while (this.hasNext()) {
++ action.accept(this.nextLong());
++ }
++ }
++ }
++
++ protected static final class ValueIterator<V> extends BaseIteratorImpl<V, V> {
++
++ protected ValueIterator(final ConcurrentLong2ReferenceChainedHashTable<V> map) {
++ super(map);
++ }
++
++ @Override
++ public V next() throws NoSuchElementException {
++ return this.nextNode().getValueVolatile();
++ }
++
++ @Override
++ public void forEachRemaining(final Consumer<? super V> action) {
++ Validate.notNull(action, "Action may not be null");
++ while (this.hasNext()) {
++ action.accept(this.next());
++ }
++ }
++ }
++
++ protected static abstract class BaseIteratorImpl<V, T> extends NodeIterator<V> implements Iterator<T> {
++
++ protected final ConcurrentLong2ReferenceChainedHashTable<V> map;
++ protected TableEntry<V> lastReturned;
++ protected TableEntry<V> nextToReturn;
++
++ protected BaseIteratorImpl(final ConcurrentLong2ReferenceChainedHashTable<V> map) {
++ super(map.table);
++ this.map = map;
++ }
++
++ @Override
++ public final boolean hasNext() {
++ if (this.nextToReturn != null) {
++ return true;
++ }
++
++ return (this.nextToReturn = this.findNext()) != null;
++ }
++
++ protected final TableEntry<V> nextNode() throws NoSuchElementException {
++ TableEntry<V> ret = this.nextToReturn;
++ if (ret != null) {
++ this.lastReturned = ret;
++ this.nextToReturn = null;
++ return ret;
++ }
++ ret = this.findNext();
++ if (ret != null) {
++ this.lastReturned = ret;
++ return ret;
++ }
++ throw new NoSuchElementException();
++ }
++
++ @Override
++ public final void remove() {
++ final TableEntry<V> lastReturned = this.nextToReturn;
++ if (lastReturned == null) {
++ throw new NoSuchElementException();
++ }
++ this.lastReturned = null;
++ this.map.remove(lastReturned.key);
++ }
++
++ @Override
++ public abstract T next() throws NoSuchElementException;
++
++ // overwritten by subclasses to avoid indirection on hasNext() and next()
++ @Override
++ public abstract void forEachRemaining(final Consumer<? super T> action);
++ }
++
++ protected static class NodeIterator<V> {
++
++ protected TableEntry<V>[] currentTable;
++ protected ResizeChain<V> resizeChain;
++ protected TableEntry<V> last;
++ protected int nextBin;
++ protected int increment;
++
++ protected NodeIterator(final TableEntry<V>[] baseTable) {
++ this.currentTable = baseTable;
++ this.increment = 1;
++ }
++
++ private TableEntry<V>[] pullResizeChain(final int index) {
++ final ResizeChain<V> resizeChain = this.resizeChain;
++ if (resizeChain == null) {
++ this.currentTable = null;
++ return null;
++ }
++ final TableEntry<V>[] newTable = resizeChain.table;
++ if (newTable == null) {
++ this.currentTable = null;
++ return null;
++ }
++
++ // the increment is a multiple of table.length, so we can undo the increments on idx by taking the
++ // mod
++ int newIdx = index & (newTable.length - 1);
++
++ final ResizeChain<V> newChain = this.resizeChain = resizeChain.prev;
++ final TableEntry<V>[] prevTable = newChain.table;
++ final int increment;
++ if (prevTable == null) {
++ increment = 1;
++ } else {
++ increment = prevTable.length;
++ }
++
++ // done with the upper table, so we can skip the resize node
++ newIdx += increment;
++
++ this.increment = increment;
++ this.nextBin = newIdx;
++ this.currentTable = newTable;
++
++ return newTable;
++ }
++
++ private TableEntry<V>[] pushResizeChain(final TableEntry<V>[] table, final TableEntry<V> entry) {
++ final ResizeChain<V> chain = this.resizeChain;
++
++ if (chain == null) {
++ final TableEntry<V>[] nextTable = (TableEntry<V>[])entry.getValuePlain();
++
++ final ResizeChain<V> oldChain = new ResizeChain<>(table, null, null);
++ final ResizeChain<V> currChain = new ResizeChain<>(nextTable, oldChain, null);
++ oldChain.next = currChain;
++
++ this.increment = table.length;
++ this.resizeChain = currChain;
++ this.currentTable = nextTable;
++
++ return nextTable;
++ } else {
++ ResizeChain<V> currChain = chain.next;
++ if (currChain == null) {
++ final TableEntry<V>[] ret = (TableEntry<V>[])entry.getValuePlain();
++ currChain = new ResizeChain<>(ret, chain, null);
++ chain.next = currChain;
++
++ this.increment = table.length;
++ this.resizeChain = currChain;
++ this.currentTable = ret;
++
++ return ret;
++ } else {
++ this.increment = table.length;
++ this.resizeChain = currChain;
++ return this.currentTable = currChain.table;
++ }
++ }
++ }
++
++ protected final TableEntry<V> findNext() {
++ for (;;) {
++ final TableEntry<V> last = this.last;
++ if (last != null) {
++ final TableEntry<V> next = last.getNextVolatile();
++ if (next != null) {
++ this.last = next;
++ if (next.getValuePlain() == null) {
++ // compute() node not yet available
++ continue;
++ }
++ return next;
++ }
++ }
++
++ TableEntry<V>[] table = this.currentTable;
++
++ if (table == null) {
++ return null;
++ }
++
++ int idx = this.nextBin;
++ int increment = this.increment;
++ for (;;) {
++ if (idx >= table.length) {
++ table = this.pullResizeChain(idx);
++ idx = this.nextBin;
++ increment = this.increment;
++ if (table != null) {
++ continue;
++ } else {
++ this.last = null;
++ return null;
++ }
++ }
++
++ final TableEntry<V> entry = getAtIndexVolatile(table, idx);
++ if (entry == null) {
++ idx += increment;
++ continue;
++ }
++
++ if (entry.resize) {
++ // push onto resize chain
++ table = this.pushResizeChain(table, entry);
++ increment = this.increment;
++ continue;
++ }
++
++ this.last = entry;
++ this.nextBin = idx + increment;
++ if (entry.getValuePlain() != null) {
++ return entry;
++ } else {
++ // compute() node not yet available
++ break;
++ }
++ }
++ }
++ }
++
++ protected static final class ResizeChain<V> {
++
++ protected final TableEntry<V>[] table;
++ protected final ResizeChain<V> prev;
++ protected ResizeChain<V> next;
++
++ protected ResizeChain(final TableEntry<V>[] table, final ResizeChain<V> prev, final ResizeChain<V> next) {
++ this.table = table;
++ this.next = next;
++ this.prev = prev;
++ }
++ }
++ }
++
++ public static final class TableEntry<V> {
++
++ protected static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class);
++
++ protected final boolean resize;
++
++ protected final long key;
++
++ protected volatile V value;
++ protected static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class);
++
++ protected final V getValuePlain() {
++ //noinspection unchecked
++ return (V)VALUE_HANDLE.get(this);
++ }
++
++ protected final V getValueAcquire() {
++ //noinspection unchecked
++ return (V)VALUE_HANDLE.getAcquire(this);
++ }
++
++ protected final V getValueVolatile() {
++ //noinspection unchecked
++ return (V)VALUE_HANDLE.getVolatile(this);
++ }
++
++ protected final void setValuePlain(final V value) {
++ VALUE_HANDLE.set(this, (Object)value);
++ }
++
++ protected final void setValueRelease(final V value) {
++ VALUE_HANDLE.setRelease(this, (Object)value);
++ }
++
++ protected final void setValueVolatile(final V value) {
++ VALUE_HANDLE.setVolatile(this, (Object)value);
++ }
++
++ protected volatile TableEntry<V> next;
++ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class);
++
++ protected final TableEntry<V> getNextPlain() {
++ //noinspection unchecked
++ return (TableEntry<V>)NEXT_HANDLE.get(this);
++ }
++
++ protected final TableEntry<V> getNextVolatile() {
++ //noinspection unchecked
++ return (TableEntry<V>)NEXT_HANDLE.getVolatile(this);
++ }
++
++ protected final void setNextPlain(final TableEntry<V> next) {
++ NEXT_HANDLE.set(this, next);
++ }
++
++ protected final void setNextRelease(final TableEntry<V> next) {
++ NEXT_HANDLE.setRelease(this, next);
++ }
++
++ protected final void setNextVolatile(final TableEntry<V> next) {
++ NEXT_HANDLE.setVolatile(this, next);
++ }
++
++ public TableEntry(final long key, final V value) {
++ this.resize = false;
++ this.key = key;
++ this.setValuePlain(value);
++ }
++
++ public TableEntry(final long key, final V value, final boolean resize) {
++ this.resize = resize;
++ this.key = key;
++ this.setValuePlain(value);
++ }
++
++ public long getKey() {
++ return this.key;
++ }
++
++ public V getValue() {
++ return this.getValueVolatile();
++ }
++ }
++}
diff --git a/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRHashTable.java b/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRHashTable.java
new file mode 100644
-index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b9a40f9f5
+index 0000000000000000000000000000000000000000..83965350d292ccf42a34520d84dcda3f88146cff
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRHashTable.java
-@@ -0,0 +1,1673 @@
+@@ -0,0 +1,1656 @@
+package ca.spottedleaf.concurrentutil.map;
+
-+import ca.spottedleaf.concurrentutil.util.ArrayUtil;
+import ca.spottedleaf.concurrentutil.util.CollectionUtil;
+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
++import ca.spottedleaf.concurrentutil.util.HashUtil;
++import ca.spottedleaf.concurrentutil.util.IntegerUtil;
+import ca.spottedleaf.concurrentutil.util.Validate;
-+import io.papermc.paper.util.IntegerUtil;
+import java.lang.invoke.VarHandle;
+import java.util.ArrayList;
+import java.util.Arrays;
@@ -3724,7 +5926,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ /**
+ * Constructs this map with the specified capacity and load factor.
+ * @param capacity specified capacity, > 0
-+ * @param loadFactor specified load factor, > 0 and finite
++ * @param loadFactor specified load factor, > 0 && finite
+ */
+ public SWMRHashTable(final int capacity, final float loadFactor) {
+ final int tableSize = getCapacityFor(capacity);
@@ -3772,7 +5974,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ * with the specified load factor.
+ * All of the specified map's entries are copied into this map.
+ * @param capacity specified capacity, > 0
-+ * @param loadFactor specified load factor, > 0 and finite
++ * @param loadFactor specified load factor, > 0 && finite
+ * @param other The specified map.
+ */
+ public SWMRHashTable(final int capacity, final float loadFactor, final Map<K, V> other) {
@@ -3780,6 +5982,15 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ this.putAll(other);
+ }
+
++ protected static <K, V> TableEntry<K, V> getAtIndexOpaque(final TableEntry<K, V>[] table, final int index) {
++ // noinspection unchecked
++ return (TableEntry<K, V>)TableEntry.TABLE_ENTRY_ARRAY_HANDLE.getOpaque(table, index);
++ }
++
++ protected static <K, V> void setAtIndexRelease(final TableEntry<K, V>[] table, final int index, final TableEntry<K, V> value) {
++ TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setRelease(table, index, value);
++ }
++
+ public final float getLoadFactor() {
+ return this.loadFactor;
+ }
@@ -3799,7 +6010,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ final int hash = SWMRHashTable.getHash(key);
+ final TableEntry<K, V>[] table = this.getTableAcquire();
+
-+ for (TableEntry<K, V> curr = ArrayUtil.getOpaque(table, hash & (table.length - 1)); curr != null; curr = curr.getNextOpaque()) {
++ for (TableEntry<K, V> curr = getAtIndexOpaque(table, hash & (table.length - 1)); curr != null; curr = curr.getNextOpaque()) {
+ if (hash == curr.hash && (key == curr.key || curr.key.equals(key))) {
+ return curr;
+ }
@@ -3826,15 +6037,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ /** must be deterministic given a key */
+ private static int getHash(final Object key) {
+ int hash = key == null ? 0 : key.hashCode();
-+ // inlined IntegerUtil#hash0
-+ hash *= 0x36935555;
-+ hash ^= hash >>> 16;
-+ return hash;
-+ }
-+
-+ static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
-+ static final int spread(int h) {
-+ return (h ^ (h >>> 16)) & HASH_BITS;
++ return HashUtil.mix(hash);
+ }
+
+ // rets -1 if capacity*loadFactor is too large
@@ -3856,10 +6059,9 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ return true;
+ }
+ /* Make no attempt to deal with concurrent modifications */
-+ if (!(obj instanceof Map)) {
++ if (!(obj instanceof Map<?, ?> other)) {
+ return false;
+ }
-+ final Map<?, ?> other = (Map<?, ?>)obj;
+
+ if (this.size() != other.size()) {
+ return false;
@@ -3868,7 +6070,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ final TableEntry<K, V>[] table = this.getTableAcquire();
+
+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<K, V> curr = ArrayUtil.getOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
++ for (TableEntry<K, V> curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
+ final V value = curr.getValueAcquire();
+
+ final Object otherValue = other.get(curr.key);
@@ -3891,7 +6093,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ final TableEntry<K, V>[] table = this.getTableAcquire();
+
+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<K, V> curr = ArrayUtil.getOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
++ for (TableEntry<K, V> curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
+ hash += curr.hashCode();
+ }
+ }
@@ -3905,7 +6107,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder(64);
-+ builder.append("SingleWriterMultiReaderHashMap:{");
++ builder.append("SWMRHashTable:{");
+
+ this.forEach((final K key, final V value) -> {
+ builder.append("{key: \"").append(key).append("\", value: \"").append(value).append("\"}");
@@ -3926,7 +6128,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ * {@inheritDoc}
+ */
+ @Override
-+ public Iterator<Entry<K, V>> iterator() {
++ public Iterator<Map.Entry<K, V>> iterator() {
+ return new EntryIterator<>(this.getTableAcquire(), this);
+ }
+
@@ -3934,12 +6136,12 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ * {@inheritDoc}
+ */
+ @Override
-+ public void forEach(final Consumer<? super Entry<K, V>> action) {
++ public void forEach(final Consumer<? super Map.Entry<K, V>> action) {
+ Validate.notNull(action, "Null action");
+
+ final TableEntry<K, V>[] table = this.getTableAcquire();
+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<K, V> curr = ArrayUtil.getOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
++ for (TableEntry<K, V> curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
+ action.accept(curr);
+ }
+ }
@@ -3954,7 +6156,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+
+ final TableEntry<K, V>[] table = this.getTableAcquire();
+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<K, V> curr = ArrayUtil.getOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
++ for (TableEntry<K, V> curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
+ final V value = curr.getValueAcquire();
+
+ action.accept(curr.key, value);
@@ -3971,7 +6173,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+
+ final TableEntry<K, V>[] table = this.getTableAcquire();
+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<K, V> curr = ArrayUtil.getOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
++ for (TableEntry<K, V> curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
+ action.accept(curr.key);
+ }
+ }
@@ -3986,7 +6188,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+
+ final TableEntry<K, V>[] table = this.getTableAcquire();
+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<K, V> curr = ArrayUtil.getOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
++ for (TableEntry<K, V> curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
+ final V value = curr.getValueAcquire();
+
+ action.accept(value);
@@ -4046,7 +6248,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+
+ final TableEntry<K, V>[] table = this.getTableAcquire();
+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<K, V> curr = ArrayUtil.getOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
++ for (TableEntry<K, V> curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
+ final V currVal = curr.getValueAcquire();
+ if (currVal == value || currVal.equals(value)) {
+ return true;
@@ -4086,9 +6288,9 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ return this.getSizeAcquire() == 0;
+ }
+
-+ protected Set<K> keyset;
-+ protected Collection<V> values;
-+ protected Set<Map.Entry<K, V>> entrySet;
++ protected KeySet<K, V> keyset;
++ protected ValueCollection<K, V> values;
++ protected EntrySet<K, V> entrySet;
+
+ @Override
+ public Set<K> keySet() {
@@ -4187,7 +6389,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ final TableEntry<K, V> head = table[index];
+ if (head == null) {
+ final TableEntry<K, V> insert = new TableEntry<>(hash, key, value);
-+ ArrayUtil.setRelease(table, index, insert);
++ setAtIndexRelease(table, index, insert);
+ this.addToSize(1);
+ return null;
+ }
@@ -4242,7 +6444,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ ++removed;
+ this.removeFromSizePlain(1); /* required in case predicate throws an exception */
+
-+ ArrayUtil.setRelease(table, i, curr = curr.getNextPlain());
++ setAtIndexRelease(table, i, curr = curr.getNextPlain());
+
+ if (curr == null) {
+ continue bin_iteration_loop;
@@ -4276,7 +6478,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ * @param predicate The predicate to test key-value pairs against.
+ * @return The total number of key-value pairs removed from this map.
+ */
-+ public int removeEntryIf(final Predicate<? super Entry<K, V>> predicate) {
++ public int removeEntryIf(final Predicate<? super Map.Entry<K, V>> predicate) {
+ Validate.notNull(predicate, "Null predicate");
+
+ int removed = 0;
@@ -4295,7 +6497,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ ++removed;
+ this.removeFromSizePlain(1); /* required in case predicate throws an exception */
+
-+ ArrayUtil.setRelease(table, i, curr = curr.getNextPlain());
++ setAtIndexRelease(table, i, curr = curr.getNextPlain());
+
+ if (curr == null) {
+ continue bin_iteration_loop;
@@ -4369,7 +6571,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ return false;
+ }
+
-+ ArrayUtil.setRelease(table, index, head.getNextPlain());
++ setAtIndexRelease(table, index, head.getNextPlain());
+ this.removeFromSize(1);
+
+ return true;
@@ -4403,7 +6605,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ }
+
+ if (hash == head.hash && (head.key == key || head.key.equals(key))) {
-+ ArrayUtil.setRelease(table, index, head.getNextPlain());
++ setAtIndexRelease(table, index, head.getNextPlain());
+ this.removeFromSize(1);
+
+ return head.getValuePlain();
@@ -4541,7 +6743,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+
+ final TableEntry<K, V> insert = new TableEntry<>(hash, key, newVal);
+ if (prev == null) {
-+ ArrayUtil.setRelease(table, index, insert);
++ setAtIndexRelease(table, index, insert);
+ } else {
+ prev.setNextRelease(insert);
+ }
@@ -4560,7 +6762,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ }
+
+ if (prev == null) {
-+ ArrayUtil.setRelease(table, index, curr.getNextPlain());
++ setAtIndexRelease(table, index, curr.getNextPlain());
+ } else {
+ prev.setNextRelease(curr.getNextPlain());
+ }
@@ -4596,7 +6798,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ }
+
+ if (prev == null) {
-+ ArrayUtil.setRelease(table, index, curr.getNextPlain());
++ setAtIndexRelease(table, index, curr.getNextPlain());
+ } else {
+ prev.setNextRelease(curr.getNextPlain());
+ }
@@ -4637,7 +6839,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+
+ final TableEntry<K, V> insert = new TableEntry<>(hash, key, newVal);
+ if (prev == null) {
-+ ArrayUtil.setRelease(table, index, insert);
++ setAtIndexRelease(table, index, insert);
+ } else {
+ prev.setNextRelease(insert);
+ }
@@ -4665,7 +6867,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ if (curr == null) {
+ final TableEntry<K, V> insert = new TableEntry<>(hash, key, value);
+ if (prev == null) {
-+ ArrayUtil.setRelease(table, index, insert);
++ setAtIndexRelease(table, index, insert);
+ } else {
+ prev.setNextRelease(insert);
+ }
@@ -4684,7 +6886,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ }
+
+ if (prev == null) {
-+ ArrayUtil.setRelease(table, index, curr.getNextPlain());
++ setAtIndexRelease(table, index, curr.getNextPlain());
+ } else {
+ prev.setNextRelease(curr.getNextPlain());
+ }
@@ -4698,6 +6900,8 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+
+ protected static final class TableEntry<K, V> implements Map.Entry<K, V> {
+
++ protected static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class);
++
+ protected final int hash;
+ protected final K key;
+ protected V value;
@@ -4749,35 +6953,19 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ this.value = value;
+ }
+
-+ /**
-+ * {@inheritDoc}
-+ */
+ @Override
+ public K getKey() {
+ return this.key;
+ }
+
-+ /**
-+ * {@inheritDoc}
-+ */
+ @Override
+ public V getValue() {
+ return this.getValueAcquire();
+ }
+
-+ /**
-+ * {@inheritDoc}
-+ */
+ @Override
+ public V setValue(final V value) {
-+ if (value == null) {
-+ throw new NullPointerException();
-+ }
-+
-+ final V curr = this.getValuePlain();
-+
-+ this.setValueRelease(value);
-+ return curr;
++ throw new UnsupportedOperationException();
+ }
+
+ protected static int hash(final Object key, final Object value) {
@@ -4801,10 +6989,9 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ return true;
+ }
+
-+ if (!(obj instanceof Map.Entry)) {
++ if (!(obj instanceof Map.Entry<?, ?> other)) {
+ return false;
+ }
-+ final Map.Entry<?, ?> other = (Map.Entry<?, ?>)obj;
+ final Object otherKey = other.getKey();
+ final Object otherValue = other.getValue();
+
@@ -4832,7 +7019,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ this.map = map;
+ int tableIndex = 0;
+ for (int len = table.length; tableIndex < len; ++tableIndex) {
-+ final TableEntry<K, V> entry = ArrayUtil.getOpaque(table, tableIndex);
++ final TableEntry<K, V> entry = getAtIndexOpaque(table, tableIndex);
+ if (entry != null) {
+ this.nextEntry = entry;
+ this.tableIndex = tableIndex + 1;
@@ -4870,7 +7057,7 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+
+ // nothing in chain, so find next available bin
+ for (;tableIndex < tableLength; ++tableIndex) {
-+ next = ArrayUtil.getOpaque(table, tableIndex);
++ next = getAtIndexOpaque(table, tableIndex);
+ if (next != null) {
+ this.nextEntry = next;
+ this.tableIndex = tableIndex + 1;
@@ -5082,10 +7269,9 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+
+ @Override
+ public boolean remove(final Object object) {
-+ if (!(object instanceof Map.Entry<?, ?>)) {
++ if (!(object instanceof Map.Entry<?, ?> entry)) {
+ return false;
+ }
-+ final Map.Entry<?, ?> entry = (Map.Entry<?, ?>)object;
+
+ final Object key;
+ final Object value;
@@ -5117,21 +7303,20 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+ }
+
+ @Override
-+ public Iterator<Entry<K, V>> iterator() {
++ public Iterator<Map.Entry<K, V>> iterator() {
+ return new EntryIterator<>(this.map.getTableAcquire(), this.map);
+ }
+
+ @Override
-+ public void forEach(final Consumer<? super Entry<K, V>> action) {
++ public void forEach(final Consumer<? super Map.Entry<K, V>> action) {
+ this.map.forEach(action);
+ }
+
+ @Override
+ public boolean contains(final Object object) {
-+ if (!(object instanceof Map.Entry)) {
++ if (!(object instanceof Map.Entry<?, ?> entry)) {
+ return false;
+ }
-+ final Map.Entry<?, ?> entry = (Map.Entry<?, ?>)object;
+
+ final Object key;
+ final Object value;
@@ -5275,16 +7460,17 @@ index 0000000000000000000000000000000000000000..4289b984badd6f9167c86193454a630b
+}
diff --git a/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRLong2ObjectHashTable.java b/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRLong2ObjectHashTable.java
new file mode 100644
-index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e93320b0f7c34
+index 0000000000000000000000000000000000000000..bb301a9f4e3ac919552eef68afc73569d50674db
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRLong2ObjectHashTable.java
-@@ -0,0 +1,672 @@
+@@ -0,0 +1,674 @@
+package ca.spottedleaf.concurrentutil.map;
+
-+import ca.spottedleaf.concurrentutil.util.ArrayUtil;
++import ca.spottedleaf.concurrentutil.function.BiLongObjectConsumer;
+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
++import ca.spottedleaf.concurrentutil.util.HashUtil;
++import ca.spottedleaf.concurrentutil.util.IntegerUtil;
+import ca.spottedleaf.concurrentutil.util.Validate;
-+import io.papermc.paper.util.IntegerUtil;
+import java.lang.invoke.VarHandle;
+import java.util.Arrays;
+import java.util.function.Consumer;
@@ -5370,7 +7556,7 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+ /**
+ * Constructs this map with the specified capacity and load factor.
+ * @param capacity specified capacity, > 0
-+ * @param loadFactor specified load factor, > 0 and finite
++ * @param loadFactor specified load factor, > 0 && finite
+ */
+ public SWMRLong2ObjectHashTable(final int capacity, final float loadFactor) {
+ final int tableSize = getCapacityFor(capacity);
@@ -5418,7 +7604,7 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+ * with the specified load factor.
+ * All of the specified map's entries are copied into this map.
+ * @param capacity specified capacity, > 0
-+ * @param loadFactor specified load factor, > 0 and finite
++ * @param loadFactor specified load factor, > 0 && finite
+ * @param other The specified map.
+ */
+ public SWMRLong2ObjectHashTable(final int capacity, final float loadFactor, final SWMRLong2ObjectHashTable<V> other) {
@@ -5426,6 +7612,15 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+ this.putAll(other);
+ }
+
++ protected static <V> TableEntry<V> getAtIndexOpaque(final TableEntry<V>[] table, final int index) {
++ // noinspection unchecked
++ return (TableEntry<V>)TableEntry.TABLE_ENTRY_ARRAY_HANDLE.getOpaque(table, index);
++ }
++
++ protected static <V> void setAtIndexRelease(final TableEntry<V>[] table, final int index, final TableEntry<V> value) {
++ TableEntry.TABLE_ENTRY_ARRAY_HANDLE.setRelease(table, index, value);
++ }
++
+ public final float getLoadFactor() {
+ return this.loadFactor;
+ }
@@ -5445,7 +7640,7 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+ final int hash = SWMRLong2ObjectHashTable.getHash(key);
+ final TableEntry<V>[] table = this.getTableAcquire();
+
-+ for (TableEntry<V> curr = ArrayUtil.getOpaque(table, hash & (table.length - 1)); curr != null; curr = curr.getNextOpaque()) {
++ for (TableEntry<V> curr = getAtIndexOpaque(table, hash & (table.length - 1)); curr != null; curr = curr.getNextOpaque()) {
+ if (key == curr.key) {
+ return curr;
+ }
@@ -5471,7 +7666,7 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+
+ /** must be deterministic given a key */
+ protected static int getHash(final long key) {
-+ return (int)it.unimi.dsi.fastutil.HashCommon.mix(key);
++ return (int)HashUtil.mix(key);
+ }
+
+ // rets -1 if capacity*loadFactor is too large
@@ -5493,10 +7688,9 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+ return true;
+ }
+ /* Make no attempt to deal with concurrent modifications */
-+ if (!(obj instanceof SWMRLong2ObjectHashTable)) {
++ if (!(obj instanceof SWMRLong2ObjectHashTable<?> other)) {
+ return false;
+ }
-+ final SWMRLong2ObjectHashTable<?> other = (SWMRLong2ObjectHashTable<?>)obj;
+
+ if (this.size() != other.size()) {
+ return false;
@@ -5505,7 +7699,7 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+ final TableEntry<V>[] table = this.getTableAcquire();
+
+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<V> curr = ArrayUtil.getOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
++ for (TableEntry<V> curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
+ final V value = curr.getValueAcquire();
+
+ final Object otherValue = other.get(curr.key);
@@ -5528,7 +7722,7 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+ final TableEntry<V>[] table = this.getTableAcquire();
+
+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<V> curr = ArrayUtil.getOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
++ for (TableEntry<V> curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
+ hash += curr.hashCode();
+ }
+ }
@@ -5562,22 +7756,17 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+ /**
+ * {@inheritDoc}
+ */
-+ public void forEach(final Consumer<? super SWMRLong2ObjectHashTable.TableEntry<V>> action) {
++ public void forEach(final Consumer<? super TableEntry<V>> action) {
+ Validate.notNull(action, "Null action");
+
+ final TableEntry<V>[] table = this.getTableAcquire();
+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<V> curr = ArrayUtil.getOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
++ for (TableEntry<V> curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
+ action.accept(curr);
+ }
+ }
+ }
+
-+ @FunctionalInterface
-+ public static interface BiLongObjectConsumer<V> {
-+ public void accept(final long key, final V value);
-+ }
-+
+ /**
+ * {@inheritDoc}
+ */
@@ -5586,7 +7775,7 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+
+ final TableEntry<V>[] table = this.getTableAcquire();
+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<V> curr = ArrayUtil.getOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
++ for (TableEntry<V> curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
+ final V value = curr.getValueAcquire();
+
+ action.accept(curr.key, value);
@@ -5603,7 +7792,7 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+
+ final TableEntry<V>[] table = this.getTableAcquire();
+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<V> curr = ArrayUtil.getOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
++ for (TableEntry<V> curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
+ action.accept(curr.key);
+ }
+ }
@@ -5618,7 +7807,7 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+
+ final TableEntry<V>[] table = this.getTableAcquire();
+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<V> curr = ArrayUtil.getOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
++ for (TableEntry<V> curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
+ final V value = curr.getValueAcquire();
+
+ action.accept(value);
@@ -5739,7 +7928,7 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+ final TableEntry<V> head = table[index];
+ if (head == null) {
+ final TableEntry<V> insert = new TableEntry<>(key, value);
-+ ArrayUtil.setRelease(table, index, insert);
++ setAtIndexRelease(table, index, insert);
+ this.addToSize(1);
+ return null;
+ }
@@ -5797,7 +7986,7 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+ }
+
+ if (head.key == key) {
-+ ArrayUtil.setRelease(table, index, head.getNextPlain());
++ setAtIndexRelease(table, index, head.getNextPlain());
+ this.removeFromSize(1);
+
+ return head.getValuePlain();
@@ -5815,6 +8004,44 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+ return null;
+ }
+
++ protected final V remove(final long key, final int hash, final V expect) {
++ final TableEntry<V>[] table = this.getTablePlain();
++ final int index = (table.length - 1) & hash;
++
++ final TableEntry<V> head = table[index];
++ if (head == null) {
++ return null;
++ }
++
++ if (head.key == key) {
++ final V val = head.value;
++ if (val == expect || val.equals(expect)) {
++ setAtIndexRelease(table, index, head.getNextPlain());
++ this.removeFromSize(1);
++
++ return head.getValuePlain();
++ } else {
++ return null;
++ }
++ }
++
++ for (TableEntry<V> curr = head.getNextPlain(), prev = head; curr != null; prev = curr, curr = curr.getNextPlain()) {
++ if (key == curr.key) {
++ final V val = curr.value;
++ if (val == expect || val.equals(expect)) {
++ prev.setNextRelease(curr.getNextPlain());
++ this.removeFromSize(1);
++
++ return curr.getValuePlain();
++ } else {
++ return null;
++ }
++ }
++ }
++
++ return null;
++ }
++
+ /**
+ * {@inheritDoc}
+ */
@@ -5822,6 +8049,10 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+ return this.remove(key, SWMRLong2ObjectHashTable.getHash(key));
+ }
+
++ public boolean remove(final long key, final V expect) {
++ return this.remove(key, SWMRLong2ObjectHashTable.getHash(key), expect) != null;
++ }
++
+ /**
+ * {@inheritDoc}
+ */
@@ -5847,6 +8078,8 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+
+ public static final class TableEntry<V> {
+
++ protected static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class);
++
+ protected final long key;
+ protected V value;
+
@@ -5903,51 +8136,847 @@ index 0000000000000000000000000000000000000000..94fca3c9b31ca4e40688209e419e9332
+ public V getValue() {
+ return this.getValueAcquire();
+ }
++ }
++}
+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
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java
+@@ -0,0 +1,558 @@
++package ca.spottedleaf.concurrentutil.scheduler;
+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ public V setValue(final V value) {
-+ if (value == null) {
-+ throw new NullPointerException();
++import ca.spottedleaf.concurrentutil.set.LinkedSortedSet;
++import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
++import ca.spottedleaf.concurrentutil.util.TimeUtil;
++import java.lang.invoke.VarHandle;
++import java.util.BitSet;
++import java.util.Comparator;
++import java.util.PriorityQueue;
++import java.util.concurrent.ThreadFactory;
++import java.util.concurrent.atomic.AtomicInteger;
++import java.util.concurrent.atomic.AtomicLong;
++import java.util.concurrent.locks.LockSupport;
++import java.util.function.BooleanSupplier;
++
++public class SchedulerThreadPool {
++
++ public static final long DEADLINE_NOT_SET = Long.MIN_VALUE;
++
++ private static final Comparator<SchedulableTick> TICK_COMPARATOR_BY_TIME = (final SchedulableTick t1, final SchedulableTick t2) -> {
++ final int timeCompare = TimeUtil.compareTimes(t1.scheduledStart, t2.scheduledStart);
++ if (timeCompare != 0) {
++ return timeCompare;
++ }
++
++ return Long.compare(t1.id, t2.id);
++ };
++
++ private final TickThreadRunner[] runners;
++ private final Thread[] threads;
++ private final LinkedSortedSet<SchedulableTick> awaiting = new LinkedSortedSet<>(TICK_COMPARATOR_BY_TIME);
++ private final PriorityQueue<SchedulableTick> queued = new PriorityQueue<>(TICK_COMPARATOR_BY_TIME);
++ private final BitSet idleThreads;
++
++ private final Object scheduleLock = new Object();
++
++ private volatile boolean halted;
++
++ /**
++ * Creates, but does not start, a scheduler thread pool with the specified number of threads
++ * created using the specified thread factory.
++ * @param threads Specified number of threads
++ * @param threadFactory Specified thread factory
++ * @see #start()
++ */
++ public SchedulerThreadPool(final int threads, final ThreadFactory threadFactory) {
++ final BitSet idleThreads = new BitSet(threads);
++ for (int i = 0; i < threads; ++i) {
++ idleThreads.set(i);
++ }
++ this.idleThreads = idleThreads;
++
++ final TickThreadRunner[] runners = new TickThreadRunner[threads];
++ final Thread[] t = new Thread[threads];
++ for (int i = 0; i < threads; ++i) {
++ runners[i] = new TickThreadRunner(i, this);
++ t[i] = threadFactory.newThread(runners[i]);
++ }
++
++ this.threads = t;
++ this.runners = runners;
++ }
++
++ /**
++ * Starts the threads in this pool.
++ */
++ public void start() {
++ for (final Thread thread : this.threads) {
++ thread.start();
++ }
++ }
++
++ /**
++ * Attempts to prevent further execution of tasks, optionally waiting for the scheduler threads to die.
++ *
++ * @param sync Whether to wait for the scheduler threads to die.
++ * @param maxWaitNS The maximum time, in ns, to wait for the scheduler threads to die.
++ * @return {@code true} if sync was false, or if sync was true and the scheduler threads died before the timeout.
++ * Otherwise, returns {@code false} if the time elapsed exceeded the maximum wait time.
++ */
++ public boolean halt(final boolean sync, final long maxWaitNS) {
++ this.halted = true;
++ for (final Thread thread : this.threads) {
++ // force response to halt
++ LockSupport.unpark(thread);
++ }
++ final long time = System.nanoTime();
++ if (sync) {
++ // start at 10 * 0.5ms -> 5ms
++ for (long failures = 9L;; failures = ConcurrentUtil.linearLongBackoff(failures, 500_000L, 50_000_000L)) {
++ boolean allDead = true;
++ for (final Thread thread : this.threads) {
++ if (thread.isAlive()) {
++ allDead = false;
++ break;
++ }
++ }
++ if (allDead) {
++ return true;
++ }
++ if ((System.nanoTime() - time) >= maxWaitNS) {
++ return false;
++ }
+ }
++ }
+
-+ final V curr = this.getValuePlain();
++ return true;
++ }
+
-+ this.setValueRelease(value);
-+ return curr;
++ /**
++ * Returns an array of the underlying scheduling threads.
++ */
++ public Thread[] getThreads() {
++ return this.threads.clone();
++ }
++
++ private void insertFresh(final SchedulableTick task) {
++ final TickThreadRunner[] runners = this.runners;
++
++ final int firstIdleThread = this.idleThreads.nextSetBit(0);
++
++ if (firstIdleThread != -1) {
++ // push to idle thread
++ this.idleThreads.clear(firstIdleThread);
++ final TickThreadRunner runner = runners[firstIdleThread];
++ task.awaitingLink = this.awaiting.addLast(task);
++ runner.acceptTask(task);
++ return;
++ }
++
++ // try to replace the last awaiting task
++ final SchedulableTick last = this.awaiting.last();
++
++ if (last != null && TICK_COMPARATOR_BY_TIME.compare(task, last) < 0) {
++ // need to replace the last task
++ this.awaiting.pollLast();
++ last.awaitingLink = null;
++ task.awaitingLink = this.awaiting.addLast(task);
++ // need to add task to queue to be picked up later
++ this.queued.add(last);
++
++ final TickThreadRunner runner = last.ownedBy;
++ runner.replaceTask(task);
++
++ return;
++ }
++
++ // add to queue, will be picked up later
++ this.queued.add(task);
++ }
++
++ private void takeTask(final TickThreadRunner runner, final SchedulableTick tick) {
++ if (!this.awaiting.remove(tick.awaitingLink)) {
++ throw new IllegalStateException("Task is not in awaiting");
++ }
++ tick.awaitingLink = null;
++ }
++
++ private SchedulableTick returnTask(final TickThreadRunner runner, final SchedulableTick reschedule) {
++ if (reschedule != null) {
++ this.queued.add(reschedule);
++ }
++ final SchedulableTick ret = this.queued.poll();
++ if (ret == null) {
++ this.idleThreads.set(runner.id);
++ } else {
++ ret.awaitingLink = this.awaiting.addLast(ret);
++ }
++
++ return ret;
++ }
++
++ /**
++ * Schedules the specified task to be executed on this thread pool.
++ * @param task Specified task
++ * @throws IllegalStateException If the task is already scheduled
++ * @see SchedulableTick
++ */
++ public void schedule(final SchedulableTick task) {
++ synchronized (this.scheduleLock) {
++ if (!task.tryMarkScheduled()) {
++ throw new IllegalStateException("Task " + task + " is already scheduled or cancelled");
++ }
++
++ task.schedulerOwnedBy = this;
++
++ this.insertFresh(task);
++ }
++ }
++
++ /**
++ * Updates the tasks scheduled start to the maximum of its current scheduled start and the specified
++ * new start. If the task is not scheduled, returns {@code false}. Otherwise, returns whether the
++ * scheduled start was updated. Undefined behavior of the specified task is scheduled in another executor.
++ * @param task Specified task
++ * @param newStart Specified new start
++ */
++ public boolean updateTickStartToMax(final SchedulableTick task, final long newStart) {
++ synchronized (this.scheduleLock) {
++ if (TimeUtil.compareTimes(newStart, task.getScheduledStart()) <= 0) {
++ return false;
++ }
++ if (this.queued.remove(task)) {
++ task.setScheduledStart(newStart);
++ this.queued.add(task);
++ return true;
++ }
++ if (task.awaitingLink != null) {
++ this.awaiting.remove(task.awaitingLink);
++ task.awaitingLink = null;
++
++ // re-queue task
++ task.setScheduledStart(newStart);
++ this.queued.add(task);
++
++ // now we need to replace the task the runner was waiting for
++ final TickThreadRunner runner = task.ownedBy;
++ final SchedulableTick replace = this.queued.poll();
++
++ // replace cannot be null, since we have added a task to queued
++ if (replace != task) {
++ runner.replaceTask(replace);
++ }
++
++ return true;
++ }
++
++ return false;
++ }
++ }
++
++ /**
++ * Returns {@code null} if the task is not scheduled, returns {@code TRUE} if the task was cancelled
++ * and was queued to execute, returns {@code FALSE} if the task was cancelled but was executing.
++ */
++ public Boolean tryRetire(final SchedulableTick task) {
++ if (task.schedulerOwnedBy != this) {
++ return null;
++ }
++
++ synchronized (this.scheduleLock) {
++ if (this.queued.remove(task)) {
++ // cancelled, and no runner owns it - so return
++ return Boolean.TRUE;
++ }
++ if (task.awaitingLink != null) {
++ this.awaiting.remove(task.awaitingLink);
++ task.awaitingLink = null;
++ // here we need to replace the task the runner was waiting for
++ final TickThreadRunner runner = task.ownedBy;
++ final SchedulableTick replace = this.queued.poll();
++
++ if (replace == null) {
++ // nothing to replace with, set to idle
++ this.idleThreads.set(runner.id);
++ runner.forceIdle();
++ } else {
++ runner.replaceTask(replace);
++ }
++
++ return Boolean.TRUE;
++ }
++
++ // could not find it in queue
++ return task.tryMarkCancelled() ? Boolean.FALSE : null;
++ }
++ }
++
++ /**
++ * Indicates that intermediate tasks are available to be executed by the task.
++ * <p>
++ * Note: currently a no-op
++ * </p>
++ * @param task The specified task
++ * @see SchedulableTick
++ */
++ public void notifyTasks(final SchedulableTick task) {
++ // Not implemented
++ }
++
++ /**
++ * Represents a tickable task that can be scheduled into a {@link SchedulerThreadPool}.
++ * <p>
++ * A tickable task is expected to run on a fixed interval, which is determined by
++ * the {@link SchedulerThreadPool}.
++ * </p>
++ * <p>
++ * A tickable task can have intermediate tasks that can be executed before its tick method is ran. Instead of
++ * the {@link SchedulerThreadPool} parking in-between ticks, the scheduler will instead drain
++ * intermediate tasks from scheduled tasks. The parsing of intermediate tasks allows the scheduler to take
++ * advantage of downtime to reduce the intermediate task load from tasks once they begin ticking.
++ * </p>
++ * <p>
++ * It is guaranteed that {@link #runTick()} and {@link #runTasks(BooleanSupplier)} are never
++ * invoked in parallel.
++ * It is required that when intermediate tasks are scheduled, that {@link SchedulerThreadPool#notifyTasks(SchedulableTick)}
++ * is invoked for any scheduled task - otherwise, {@link #runTasks(BooleanSupplier)} may not be invoked to
++ * parse intermediate tasks.
++ * </p>
++ */
++ public static abstract class SchedulableTick {
++ private static final AtomicLong ID_GENERATOR = new AtomicLong();
++ public final long id = ID_GENERATOR.getAndIncrement();
++
++ private static final int SCHEDULE_STATE_NOT_SCHEDULED = 0;
++ private static final int SCHEDULE_STATE_SCHEDULED = 1;
++ private static final int SCHEDULE_STATE_CANCELLED = 2;
++
++ private final AtomicInteger scheduled = new AtomicInteger();
++ private SchedulerThreadPool schedulerOwnedBy;
++ private long scheduledStart = DEADLINE_NOT_SET;
++ private TickThreadRunner ownedBy;
++
++ private LinkedSortedSet.Link<SchedulableTick> awaitingLink;
++
++ private boolean tryMarkScheduled() {
++ return this.scheduled.compareAndSet(SCHEDULE_STATE_NOT_SCHEDULED, SCHEDULE_STATE_SCHEDULED);
+ }
+
-+ protected static int hash(final long key, final Object value) {
-+ return SWMRLong2ObjectHashTable.getHash(key) ^ (value == null ? 0 : value.hashCode());
++ private boolean tryMarkCancelled() {
++ return this.scheduled.compareAndSet(SCHEDULE_STATE_SCHEDULED, SCHEDULE_STATE_CANCELLED);
++ }
++
++ private boolean isScheduled() {
++ return this.scheduled.get() == SCHEDULE_STATE_SCHEDULED;
++ }
++
++ protected final long getScheduledStart() {
++ return this.scheduledStart;
+ }
+
+ /**
-+ * {@inheritDoc}
++ * If this task is scheduled, then this may only be invoked during {@link #runTick()},
++ * and {@link #runTasks(BooleanSupplier)}
++ */
++ protected final void setScheduledStart(final long value) {
++ this.scheduledStart = value;
++ }
++
++ /**
++ * Executes the tick.
++ * <p>
++ * It is the callee's responsibility to invoke {@link #setScheduledStart(long)} to adjust the start of
++ * the next tick.
++ * </p>
++ * @return {@code true} if the task should continue to be scheduled, {@code false} otherwise.
++ */
++ public abstract boolean runTick();
++
++ /**
++ * Returns whether this task has any intermediate tasks that can be executed.
+ */
++ public abstract boolean hasTasks();
++
++ /**
++ * Returns {@code null} if this task should not be scheduled, otherwise returns
++ * {@code Boolean.TRUE} if there are more intermediate tasks to execute and
++ * {@code Boolean.FALSE} if there are no more intermediate tasks to execute.
++ */
++ public abstract Boolean runTasks(final BooleanSupplier canContinue);
++
+ @Override
-+ public int hashCode() {
-+ return hash(this.key, this.getValueAcquire());
++ public String toString() {
++ return "SchedulableTick:{" +
++ "class=" + this.getClass().getName() + "," +
++ "scheduled_state=" + this.scheduled.get() + ","
++ + "}";
+ }
++ }
++
++ private static final class TickThreadRunner implements Runnable {
+
+ /**
-+ * {@inheritDoc}
++ * There are no tasks in this thread's runqueue, so it is parked.
++ * <p>
++ * stateTarget = null
++ * </p>
+ */
++ private static final int STATE_IDLE = 0;
++
++ /**
++ * The runner is waiting to tick a task, as it has no intermediate tasks to execute.
++ * <p>
++ * stateTarget = the task awaiting tick
++ * </p>
++ */
++ private static final int STATE_AWAITING_TICK = 1;
++
++ /**
++ * The runner is executing a tick for one of the tasks that was in its runqueue.
++ * <p>
++ * stateTarget = the task being ticked
++ * </p>
++ */
++ private static final int STATE_EXECUTING_TICK = 2;
++
++ public final int id;
++ public final SchedulerThreadPool scheduler;
++
++ private volatile Thread thread;
++ private volatile TickThreadRunnerState state = new TickThreadRunnerState(null, STATE_IDLE);
++ private static final VarHandle STATE_HANDLE = ConcurrentUtil.getVarHandle(TickThreadRunner.class, "state", TickThreadRunnerState.class);
++
++ private void setStatePlain(final TickThreadRunnerState state) {
++ STATE_HANDLE.set(this, state);
++ }
++
++ private void setStateOpaque(final TickThreadRunnerState state) {
++ STATE_HANDLE.setOpaque(this, state);
++ }
++
++ private void setStateVolatile(final TickThreadRunnerState state) {
++ STATE_HANDLE.setVolatile(this, state);
++ }
++
++ private static record TickThreadRunnerState(SchedulableTick stateTarget, int state) {}
++
++ public TickThreadRunner(final int id, final SchedulerThreadPool scheduler) {
++ this.id = id;
++ this.scheduler = scheduler;
++ }
++
++ private Thread getRunnerThread() {
++ return this.thread;
++ }
++
++ private void acceptTask(final SchedulableTick task) {
++ if (task.ownedBy != null) {
++ throw new IllegalStateException("Already owned by another runner");
++ }
++ task.ownedBy = this;
++ final TickThreadRunnerState state = this.state;
++ if (state.state != STATE_IDLE) {
++ throw new IllegalStateException("Cannot accept task in state " + state);
++ }
++ this.setStateVolatile(new TickThreadRunnerState(task, STATE_AWAITING_TICK));
++ LockSupport.unpark(this.getRunnerThread());
++ }
++
++ private void replaceTask(final SchedulableTick task) {
++ final TickThreadRunnerState state = this.state;
++ if (state.state != STATE_AWAITING_TICK) {
++ throw new IllegalStateException("Cannot replace task in state " + state);
++ }
++ if (task.ownedBy != null) {
++ throw new IllegalStateException("Already owned by another runner");
++ }
++ task.ownedBy = this;
++
++ state.stateTarget.ownedBy = null;
++
++ this.setStateVolatile(new TickThreadRunnerState(task, STATE_AWAITING_TICK));
++ LockSupport.unpark(this.getRunnerThread());
++ }
++
++ private void forceIdle() {
++ final TickThreadRunnerState state = this.state;
++ if (state.state != STATE_AWAITING_TICK) {
++ throw new IllegalStateException("Cannot replace task in state " + state);
++ }
++ state.stateTarget.ownedBy = null;
++ this.setStateOpaque(new TickThreadRunnerState(null, STATE_IDLE));
++ // no need to unpark
++ }
++
++ private boolean takeTask(final TickThreadRunnerState state, final SchedulableTick task) {
++ synchronized (this.scheduler.scheduleLock) {
++ if (this.state != state) {
++ return false;
++ }
++ this.setStatePlain(new TickThreadRunnerState(task, STATE_EXECUTING_TICK));
++ this.scheduler.takeTask(this, task);
++ return true;
++ }
++ }
++
++ private void returnTask(final SchedulableTick task, final boolean reschedule) {
++ synchronized (this.scheduler.scheduleLock) {
++ task.ownedBy = null;
++
++ final SchedulableTick newWait = this.scheduler.returnTask(this, reschedule && task.isScheduled() ? task : null);
++ if (newWait == null) {
++ this.setStatePlain(new TickThreadRunnerState(null, STATE_IDLE));
++ } else {
++ if (newWait.ownedBy != null) {
++ throw new IllegalStateException("Already owned by another runner");
++ }
++ newWait.ownedBy = this;
++ this.setStatePlain(new TickThreadRunnerState(newWait, STATE_AWAITING_TICK));
++ }
++ }
++ }
++
+ @Override
-+ public boolean equals(final Object obj) {
-+ if (this == obj) {
++ public void run() {
++ this.thread = Thread.currentThread();
++
++ main_state_loop:
++ for (;;) {
++ final TickThreadRunnerState startState = this.state;
++ final int startStateType = startState.state;
++ final SchedulableTick startStateTask = startState.stateTarget;
++
++ if (this.scheduler.halted) {
++ return;
++ }
++
++ switch (startStateType) {
++ case STATE_IDLE: {
++ while (this.state.state == STATE_IDLE) {
++ LockSupport.park();
++ if (this.scheduler.halted) {
++ return;
++ }
++ }
++ continue main_state_loop;
++ }
++
++ case STATE_AWAITING_TICK: {
++ final long deadline = startStateTask.getScheduledStart();
++ for (;;) {
++ if (this.state != startState) {
++ continue main_state_loop;
++ }
++ final long diff = deadline - System.nanoTime();
++ if (diff <= 0L) {
++ break;
++ }
++ LockSupport.parkNanos(startState, diff);
++ if (this.scheduler.halted) {
++ return;
++ }
++ }
++
++ if (!this.takeTask(startState, startStateTask)) {
++ continue main_state_loop;
++ }
++
++ // TODO exception handling
++ final boolean reschedule = startStateTask.runTick();
++
++ this.returnTask(startStateTask, reschedule);
++
++ continue main_state_loop;
++ }
++
++ case STATE_EXECUTING_TICK: {
++ throw new IllegalStateException("Tick execution must be set by runner thread, not by any other thread");
++ }
++
++ default: {
++ throw new IllegalStateException("Unknown state: " + startState);
++ }
++ }
++ }
++ }
++ }
++}
+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
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java
+@@ -0,0 +1,272 @@
++package ca.spottedleaf.concurrentutil.set;
++
++import java.util.Comparator;
++import java.util.Iterator;
++import java.util.NoSuchElementException;
++
++public final class LinkedSortedSet<E> implements Iterable<E> {
++
++ public final Comparator<? super E> comparator;
++
++ protected Link<E> head;
++ protected Link<E> tail;
++
++ public LinkedSortedSet() {
++ this((Comparator)Comparator.naturalOrder());
++ }
++
++ public LinkedSortedSet(final Comparator<? super E> comparator) {
++ this.comparator = comparator;
++ }
++
++ public void clear() {
++ this.head = this.tail = null;
++ }
++
++ public boolean isEmpty() {
++ return this.head == null;
++ }
++
++ public E first() {
++ final Link<E> head = this.head;
++ return head == null ? null : head.element;
++ }
++
++ public E last() {
++ final Link<E> tail = this.tail;
++ return tail == null ? null : tail.element;
++ }
++
++ public boolean containsFirst(final E element) {
++ final Comparator<? super E> comparator = this.comparator;
++ for (Link<E> curr = this.head; curr != null; curr = curr.next) {
++ if (comparator.compare(element, curr.element) == 0) {
+ return true;
+ }
++ }
++ return false;
++ }
+
-+ if (!(obj instanceof TableEntry<?>)) {
-+ return false;
++ public boolean containsLast(final E element) {
++ final Comparator<? super E> comparator = this.comparator;
++ for (Link<E> curr = this.tail; curr != null; curr = curr.prev) {
++ if (comparator.compare(element, curr.element) == 0) {
++ return true;
+ }
-+ final TableEntry<?> other = (TableEntry<?>)obj;
-+ final long otherKey = other.getKey();
-+ final long thisKey = this.getKey();
-+ final Object otherValue = other.getValueAcquire();
-+ final V thisVal = this.getValueAcquire();
-+ return (thisKey == otherKey) && (thisVal == otherValue || thisVal.equals(otherValue));
++ }
++ return false;
++ }
++
++ private void removeNode(final Link<E> node) {
++ final Link<E> prev = node.prev;
++ final Link<E> 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 boolean remove(final Link<E> link) {
++ if (link.element == null) {
++ return false;
++ }
++
++ this.removeNode(link);
++ return true;
++ }
++
++ public boolean removeFirst(final E element) {
++ final Comparator<? super E> comparator = this.comparator;
++ for (Link<E> curr = this.head; curr != null; curr = curr.next) {
++ if (comparator.compare(element, curr.element) == 0) {
++ this.removeNode(curr);
++ return true;
++ }
++ }
++ return false;
++ }
++
++ public boolean removeLast(final E element) {
++ final Comparator<? super E> comparator = this.comparator;
++ for (Link<E> curr = this.tail; curr != null; curr = curr.prev) {
++ if (comparator.compare(element, curr.element) == 0) {
++ this.removeNode(curr);
++ return true;
++ }
++ }
++ return false;
++ }
++
++ @Override
++ public Iterator<E> iterator() {
++ return new Iterator<>() {
++ private Link<E> next = LinkedSortedSet.this.head;
++
++ @Override
++ public boolean hasNext() {
++ return this.next != null;
++ }
++
++ @Override
++ public E next() {
++ final Link<E> next = this.next;
++ if (next == null) {
++ throw new NoSuchElementException();
++ }
++ this.next = next.next;
++ return next.element;
++ }
++ };
++ }
++
++ public E pollFirst() {
++ final Link<E> head = this.head;
++ if (head == null) {
++ return null;
++ }
++
++ final E ret = head.element;
++ final Link<E> 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<E> tail = this.tail;
++ if (tail == null) {
++ return null;
++ }
++
++ final E ret = tail.element;
++ final Link<E> 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<E> addLast(final E element) {
++ final Comparator<? super E> comparator = this.comparator;
++
++ Link<E> curr = this.tail;
++ if (curr != null) {
++ int compare;
++
++ while ((compare = comparator.compare(element, curr.element)) < 0) {
++ Link<E> prev = curr;
++ curr = curr.prev;
++ if (curr != null) {
++ continue;
++ }
++ return this.head = prev.prev = new Link<>(element, null, prev);
++ }
++
++ if (compare != 0) {
++ // insert after curr
++ final Link<E> next = curr.next;
++ final Link<E> insert = new Link<>(element, curr, next);
++ curr.next = insert;
++
++ if (next == null) {
++ this.tail = insert;
++ } else {
++ next.prev = insert;
++ }
++ return insert;
++ }
++
++ return null;
++ } else {
++ return this.head = this.tail = new Link<>(element);
++ }
++ }
++
++ public Link<E> addFirst(final E element) {
++ final Comparator<? super E> comparator = this.comparator;
++
++ Link<E> curr = this.head;
++ if (curr != null) {
++ int compare;
++
++ while ((compare = comparator.compare(element, curr.element)) > 0) {
++ Link<E> prev = curr;
++ curr = curr.next;
++ if (curr != null) {
++ continue;
++ }
++ return this.tail = prev.next = new Link<>(element, prev, null);
++ }
++
++ if (compare != 0) {
++ // insert before curr
++ final Link<E> prev = curr.prev;
++ final Link<E> insert = new Link<>(element, prev, curr);
++ curr.prev = insert;
++
++ if (prev == null) {
++ this.head = insert;
++ } else {
++ prev.next = insert;
++ }
++ return insert;
++ }
++
++ return null;
++ } else {
++ return this.head = this.tail = new Link<>(element);
++ }
++ }
++
++ public static final class Link<E> {
++ private E element;
++ private Link<E> prev;
++ private Link<E> next;
++
++ private Link() {}
++
++ private Link(final E element) {
++ this.element = element;
++ }
++
++ private Link(final E element, final Link<E> prev, final Link<E> next) {
++ this.element = element;
++ this.prev = prev;
++ this.next = next;
+ }
+ }
+}
@@ -6982,6 +10011,441 @@ index 0000000000000000000000000000000000000000..23ae82e55696a7e2ff0e0f9609c0df6a
+ return MethodHandles.arrayElementVarHandle(type);
+ }
+}
+diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/HashUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/HashUtil.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..2b9f36211d1cbb4fcf1457c0a83592499e9aa23b
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/concurrentutil/util/HashUtil.java
+@@ -0,0 +1,111 @@
++package ca.spottedleaf.concurrentutil.util;
++
++public final class HashUtil {
++
++ // Copied from fastutil HashCommon
++
++ /** 2<sup>32</sup> &middot; &phi;, &phi; = (&#x221A;5 &minus; 1)/2. */
++ private static final int INT_PHI = 0x9E3779B9;
++ /** The reciprocal of {@link #INT_PHI} modulo 2<sup>32</sup>. */
++ private static final int INV_INT_PHI = 0x144cbc89;
++ /** 2<sup>64</sup> &middot; &phi;, &phi; = (&#x221A;5 &minus; 1)/2. */
++ private static final long LONG_PHI = 0x9E3779B97F4A7C15L;
++ /** The reciprocal of {@link #LONG_PHI} modulo 2<sup>64</sup>. */
++ private static final long INV_LONG_PHI = 0xf1de83e19937733dL;
++
++ /** Avalanches the bits of an integer by applying the finalisation step of MurmurHash3.
++ *
++ * <p>This method implements the finalisation step of Austin Appleby's <a href="http://code.google.com/p/smhasher/">MurmurHash3</a>.
++ * Its purpose is to avalanche the bits of the argument to within 0.25% bias.
++ *
++ * @param x an integer.
++ * @return a hash value with good avalanching properties.
++ */
++ // additional note: this function is a bijection onto all integers
++ public static int murmurHash3(int x) {
++ x ^= x >>> 16;
++ x *= 0x85ebca6b;
++ x ^= x >>> 13;
++ x *= 0xc2b2ae35;
++ x ^= x >>> 16;
++ return x;
++ }
++
++
++ /** Avalanches the bits of a long integer by applying the finalisation step of MurmurHash3.
++ *
++ * <p>This method implements the finalisation step of Austin Appleby's <a href="http://code.google.com/p/smhasher/">MurmurHash3</a>.
++ * Its purpose is to avalanche the bits of the argument to within 0.25% bias.
++ *
++ * @param x a long integer.
++ * @return a hash value with good avalanching properties.
++ */
++ // additional note: this function is a bijection onto all longs
++ public static long murmurHash3(long x) {
++ x ^= x >>> 33;
++ x *= 0xff51afd7ed558ccdL;
++ x ^= x >>> 33;
++ x *= 0xc4ceb9fe1a85ec53L;
++ x ^= x >>> 33;
++ return x;
++ }
++
++ /** Quickly mixes the bits of an integer.
++ *
++ * <p>This method mixes the bits of the argument by multiplying by the golden ratio and
++ * xorshifting the result. It is borrowed from <a href="https://github.com/leventov/Koloboke">Koloboke</a>, and
++ * it has slightly worse behaviour than {@link #murmurHash3(int)} (in open-addressing hash tables the average number of probes
++ * is slightly larger), but it's much faster.
++ *
++ * @param x an integer.
++ * @return a hash value obtained by mixing the bits of {@code x}.
++ * @see #invMix(int)
++ */
++ // additional note: this function is a bijection onto all integers
++ public static int mix(final int x) {
++ final int h = x * INT_PHI;
++ return h ^ (h >>> 16);
++ }
++
++ /** The inverse of {@link #mix(int)}. This method is mainly useful to create unit tests.
++ *
++ * @param x an integer.
++ * @return a value that passed through {@link #mix(int)} would give {@code x}.
++ */
++ // additional note: this function is a bijection onto all integers
++ public static int invMix(final int x) {
++ return (x ^ x >>> 16) * INV_INT_PHI;
++ }
++
++ /** Quickly mixes the bits of a long integer.
++ *
++ * <p>This method mixes the bits of the argument by multiplying by the golden ratio and
++ * xorshifting twice the result. It is borrowed from <a href="https://github.com/leventov/Koloboke">Koloboke</a>, and
++ * it has slightly worse behaviour than {@link #murmurHash3(long)} (in open-addressing hash tables the average number of probes
++ * is slightly larger), but it's much faster.
++ *
++ * @param x a long integer.
++ * @return a hash value obtained by mixing the bits of {@code x}.
++ */
++ // additional note: this function is a bijection onto all longs
++ public static long mix(final long x) {
++ long h = x * LONG_PHI;
++ h ^= h >>> 32;
++ return h ^ (h >>> 16);
++ }
++
++ /** The inverse of {@link #mix(long)}. This method is mainly useful to create unit tests.
++ *
++ * @param x a long integer.
++ * @return a value that passed through {@link #mix(long)} would give {@code x}.
++ */
++ // additional note: this function is a bijection onto all longs
++ public static long invMix(long x) {
++ x ^= x >>> 32;
++ x ^= x >>> 16;
++ return (x ^ x >>> 32) * INV_LONG_PHI;
++ }
++
++
++ private HashUtil() {}
++}
+diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/IntPairUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/IntPairUtil.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..4e61c477a56e645228d5a2015c26816954d17bf8
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/concurrentutil/util/IntPairUtil.java
+@@ -0,0 +1,46 @@
++package ca.spottedleaf.concurrentutil.util;
++
++public final class IntPairUtil {
++
++ /**
++ * Packs the specified integers into one long value.
++ */
++ public static long key(final int left, final int right) {
++ return ((long)right << 32) | (left & 0xFFFFFFFFL);
++ }
++
++ /**
++ * Retrieves the left packed integer from the key
++ */
++ public static int left(final long key) {
++ return (int)key;
++ }
++
++ /**
++ * Retrieves the right packed integer from the key
++ */
++ public static int right(final long key) {
++ return (int)(key >>> 32);
++ }
++
++ public static String toString(final long key) {
++ return "{left:" + left(key) + ", right:" + right(key) + "}";
++ }
++
++ public static String toString(final long[] array, final int from, final int to) {
++ final StringBuilder ret = new StringBuilder();
++ ret.append("[");
++
++ for (int i = from; i < to; ++i) {
++ if (i != from) {
++ ret.append(", ");
++ }
++ ret.append(toString(array[i]));
++ }
++
++ ret.append("]");
++ return ret.toString();
++ }
++
++ private IntPairUtil() {}
++}
+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
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java
+@@ -0,0 +1,176 @@
++package ca.spottedleaf.concurrentutil.util;
++
++public final class IntegerUtil {
++
++ public static final int HIGH_BIT_U32 = Integer.MIN_VALUE;
++ public static final long HIGH_BIT_U64 = Long.MIN_VALUE;
++
++ public static int ceilLog2(final int value) {
++ return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros
++ }
++
++ public static long ceilLog2(final long value) {
++ return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros
++ }
++
++ public static int floorLog2(final int value) {
++ // xor is optimized subtract for 2^n -1
++ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1)
++ return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros
++ }
++
++ public static int floorLog2(final long value) {
++ // xor is optimized subtract for 2^n -1
++ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1)
++ return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros
++ }
++
++ public static int roundCeilLog2(final int value) {
++ // optimized variant of 1 << (32 - leading(val - 1))
++ // given
++ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32)
++ // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1)))
++ // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1)))
++ // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1))
++ // HIGH_BIT_32 >>> (-1 + leading(val - 1))
++ return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1);
++ }
++
++ public static long roundCeilLog2(final long value) {
++ // see logic documented above
++ return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1);
++ }
++
++ public static int roundFloorLog2(final int value) {
++ // optimized variant of 1 << (31 - leading(val))
++ // given
++ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32)
++ // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val)))
++ // HIGH_BIT_32 >> (31 - (31 - leading(val)))
++ // HIGH_BIT_32 >> (31 - 31 + leading(val))
++ return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value);
++ }
++
++ public static long roundFloorLog2(final long value) {
++ // see logic documented above
++ return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value);
++ }
++
++ public static boolean isPowerOfTwo(final int n) {
++ // 2^n has one bit
++ // note: this rets true for 0 still
++ return IntegerUtil.getTrailingBit(n) == n;
++ }
++
++ public static boolean isPowerOfTwo(final long n) {
++ // 2^n has one bit
++ // note: this rets true for 0 still
++ return IntegerUtil.getTrailingBit(n) == n;
++ }
++
++ public static int getTrailingBit(final int n) {
++ return -n & n;
++ }
++
++ public static long getTrailingBit(final long n) {
++ return -n & n;
++ }
++
++ public static int trailingZeros(final int n) {
++ return Integer.numberOfTrailingZeros(n);
++ }
++
++ public static int trailingZeros(final long n) {
++ return Long.numberOfTrailingZeros(n);
++ }
++
++ // from hacker's delight (signed division magic value)
++ public static int getDivisorMultiple(final long numbers) {
++ return (int)(numbers >>> 32);
++ }
++
++ // from hacker's delight (signed division magic value)
++ public static int getDivisorShift(final long numbers) {
++ return (int)numbers;
++ }
++
++ // copied from hacker's delight (signed division magic value)
++ // http://www.hackersdelight.org/hdcodetxt/magic.c.txt
++ public static long getDivisorNumbers(final int d) {
++ final int ad = branchlessAbs(d);
++
++ if (ad < 2) {
++ throw new IllegalArgumentException("|number| must be in [2, 2^31 -1], not: " + d);
++ }
++
++ final int two31 = 0x80000000;
++ final long mask = 0xFFFFFFFFL; // mask for enforcing unsigned behaviour
++
++ /*
++ Signed usage:
++ int number;
++ long magic = getDivisorNumbers(div);
++ long mul = magic >>> 32;
++ int sign = number >> 31;
++ int result = (int)(((long)number * mul) >>> magic) - sign;
++ */
++ /*
++ Unsigned usage: (note: fails for input > Integer.MAX_VALUE, only use when input < Integer.MAX_VALUE to avoid sign calculation)
++ int number;
++ long magic = getDivisorNumbers(div);
++ long mul = magic >>> 32;
++ int result = (int)(((long)number * mul) >>> magic);
++ */
++
++ int p = 31;
++
++ // all these variables are UNSIGNED!
++ int t = two31 + (d >>> 31);
++ int anc = t - 1 - (int)((t & mask)%ad);
++ int q1 = (int)((two31 & mask)/(anc & mask));
++ int r1 = two31 - q1*anc;
++ int q2 = (int)((two31 & mask)/(ad & mask));
++ int r2 = two31 - q2*ad;
++ int delta;
++
++ do {
++ p = p + 1;
++ q1 = 2*q1; // Update q1 = 2**p/|nc|.
++ r1 = 2*r1; // Update r1 = rem(2**p, |nc|).
++ if ((r1 & mask) >= (anc & mask)) {// (Must be an unsigned comparison here)
++ q1 = q1 + 1;
++ r1 = r1 - anc;
++ }
++ q2 = 2*q2; // Update q2 = 2**p/|d|.
++ r2 = 2*r2; // Update r2 = rem(2**p, |d|).
++ if ((r2 & mask) >= (ad & mask)) {// (Must be an unsigned comparison here)
++ q2 = q2 + 1;
++ r2 = r2 - ad;
++ }
++ delta = ad - r2;
++ } while ((q1 & mask) < (delta & mask) || (q1 == delta && r1 == 0));
++
++ int magicNum = q2 + 1;
++ if (d < 0) {
++ magicNum = -magicNum;
++ }
++ int shift = p;
++ return ((long)magicNum << 32) | shift;
++ }
++
++ public static int branchlessAbs(final int val) {
++ // -n = -1 ^ n + 1
++ final int mask = val >> (Integer.SIZE - 1); // -1 if < 0, 0 if >= 0
++ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1
++ }
++
++ public static long branchlessAbs(final long val) {
++ // -n = -1 ^ n + 1
++ final long mask = val >> (Long.SIZE - 1); // -1 if < 0, 0 if >= 0
++ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1
++ }
++
++ private IntegerUtil() {
++ throw new RuntimeException();
++ }
++}
+\ 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
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/concurrentutil/util/ThrowUtil.java
+@@ -0,0 +1,11 @@
++package ca.spottedleaf.concurrentutil.util;
++
++public final class ThrowUtil {
++
++ private ThrowUtil() {}
++
++ public static <T extends Throwable> void throwUnchecked(final Throwable thr) throws T {
++ throw (T)thr;
++ }
++
++}
+diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/TimeUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/TimeUtil.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..63688716244066581d5b505703576e3340e3baf3
+--- /dev/null
++++ b/src/main/java/ca/spottedleaf/concurrentutil/util/TimeUtil.java
+@@ -0,0 +1,60 @@
++package ca.spottedleaf.concurrentutil.util;
++
++public final class TimeUtil {
++
++ /*
++ * The comparator is not a valid comparator for every long value. To prove where it is valid, see below.
++ *
++ * For reflexivity, we have that x - x = 0. We then have that for any long value x that
++ * compareTimes(x, x) == 0, as expected.
++ *
++ * For symmetry, we have that x - y = -(y - x) except for when y - x = Long.MIN_VALUE.
++ * So, the difference between any times x and y must not be equal to Long.MIN_VALUE.
++ *
++ * As for the transitive relation, consider we have x,y such that x - y = a > 0 and z such that
++ * y - z = b > 0. Then, we will have that the x - z > 0 is equivalent to a + b > 0. For long values,
++ * this holds as long as a + b <= Long.MAX_VALUE.
++ *
++ * Also consider we have x, y such that x - y = a < 0 and z such that y - z = b < 0. Then, we will have
++ * that x - z < 0 is equivalent to a + b < 0. For long values, this holds as long as a + b >= -Long.MAX_VALUE.
++ *
++ * Thus, the comparator is only valid for timestamps such that abs(c - d) <= Long.MAX_VALUE for all timestamps
++ * c and d.
++ */
++
++ /**
++ * This function is appropriate to be used as a {@link java.util.Comparator} between two timestamps, which
++ * indicates whether the timestamps represented by t1, t2 that t1 is before, equal to, or after t2.
++ */
++ public static int compareTimes(final long t1, final long t2) {
++ final long diff = t1 - t2;
++
++ // HD, Section 2-7
++ return (int) ((diff >> 63) | (-diff >>> 63));
++ }
++
++ public static long getGreatestTime(final long t1, final long t2) {
++ final long diff = t1 - t2;
++ return diff < 0L ? t2 : t1;
++ }
++
++ public static long getLeastTime(final long t1, final long t2) {
++ final long diff = t1 - t2;
++ return diff > 0L ? t2 : t1;
++ }
++
++ public static long clampTime(final long value, final long min, final long max) {
++ final long diffMax = value - max;
++ final long diffMin = value - min;
++
++ if (diffMax > 0L) {
++ return max;
++ }
++ if (diffMin < 0L) {
++ return min;
++ }
++ return value;
++ }
++
++ private TimeUtil() {}
++}
diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/Validate.java b/src/main/java/ca/spottedleaf/concurrentutil/util/Validate.java
new file mode 100644
index 0000000000000000000000000000000000000000..382177d0d162fa3139c9078a873ce2504a2b17b2