diff options
author | Spottedleaf <[email protected]> | 2024-06-14 10:47:33 -0700 |
---|---|---|
committer | Spottedleaf <[email protected]> | 2024-06-14 10:47:33 -0700 |
commit | f5693896c5d0a3116efedcc730896be2c727038d (patch) | |
tree | 6f90be1bed8ad5698fbf3afb01b4da6fa8b5e827 | |
parent | df633e5ffad29b1ecb3260ebba03d0459aa4861a (diff) | |
download | Paper-f5693896c5d0a3116efedcc730896be2c727038d.tar.gz Paper-f5693896c5d0a3116efedcc730896be2c727038d.zip |
Update ConcurrentUtil
Mostly for the primitive long to reference hashtable impl
-rw-r--r-- | patches/server/0007-ConcurrentUtil.patch | 3914 |
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> · φ, φ = (√5 − 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> · φ, φ = (√5 − 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 |