aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0007-ConcurrentUtil.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patches/server/0007-ConcurrentUtil.patch')
-rw-r--r--patches/server/0007-ConcurrentUtil.patch10553
1 files changed, 12 insertions, 10541 deletions
diff --git a/patches/server/0007-ConcurrentUtil.patch b/patches/server/0007-ConcurrentUtil.patch
index b285b3c6e3..8e147a77db 100644
--- a/patches/server/0007-ConcurrentUtil.patch
+++ b/patches/server/0007-ConcurrentUtil.patch
@@ -4,10544 +4,15 @@ Date: Sun, 23 Jan 2022 22:58:11 -0800
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..f84a622dc29750139ac280f480b7cd132b036287
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/collection/MultiThreadedQueue.java
-@@ -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;
-+import java.util.Iterator;
-+import java.util.List;
-+import java.util.NoSuchElementException;
-+import java.util.Queue;
-+import java.util.Spliterator;
-+import java.util.Spliterators;
-+import java.util.function.Consumer;
-+import java.util.function.IntFunction;
-+import java.util.function.Predicate;
-+
-+/**
-+ * MT-Safe linked first in first out ordered queue.
-+ *
-+ * This queue should out-perform {@link java.util.concurrent.ConcurrentLinkedQueue} in high-contention reads/writes, and is
-+ * not any slower in lower contention reads/writes.
-+ * <p>
-+ * Note that this queue breaks the specification laid out by {@link Collection}, see {@link #preventAdds()} and {@link Collection#add(Object)}.
-+ * </p>
-+ * <p><b>
-+ * This queue will only unlink linked nodes through the {@link #peek()} and {@link #poll()} methods, and this is only if
-+ * they are at the head of the queue.
-+ * </b></p>
-+ * @param <E> Type of element in this queue.
-+ */
-+public class MultiThreadedQueue<E> implements Queue<E> {
-+
-+ protected volatile LinkedNode<E> head; /* Always non-null, high chance of being the actual head */
-+
-+ protected volatile LinkedNode<E> tail; /* Always non-null, high chance of being the actual tail */
-+
-+ /* Note that it is possible to reach head from tail. */
-+
-+ /* IMPL NOTE: Leave hashCode and equals to their defaults */
-+
-+ protected static final VarHandle HEAD_HANDLE = ConcurrentUtil.getVarHandle(MultiThreadedQueue.class, "head", LinkedNode.class);
-+ protected static final VarHandle TAIL_HANDLE = ConcurrentUtil.getVarHandle(MultiThreadedQueue.class, "tail", LinkedNode.class);
-+
-+ /* head */
-+
-+ protected final void setHeadPlain(final LinkedNode<E> newHead) {
-+ HEAD_HANDLE.set(this, newHead);
-+ }
-+
-+ protected final void setHeadOpaque(final LinkedNode<E> newHead) {
-+ HEAD_HANDLE.setOpaque(this, newHead);
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ protected final LinkedNode<E> getHeadPlain() {
-+ return (LinkedNode<E>)HEAD_HANDLE.get(this);
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ protected final LinkedNode<E> getHeadOpaque() {
-+ return (LinkedNode<E>)HEAD_HANDLE.getOpaque(this);
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ protected final LinkedNode<E> getHeadAcquire() {
-+ return (LinkedNode<E>)HEAD_HANDLE.getAcquire(this);
-+ }
-+
-+ /* tail */
-+
-+ protected final void setTailPlain(final LinkedNode<E> newTail) {
-+ TAIL_HANDLE.set(this, newTail);
-+ }
-+
-+ protected final void setTailOpaque(final LinkedNode<E> newTail) {
-+ TAIL_HANDLE.setOpaque(this, newTail);
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ protected final LinkedNode<E> getTailPlain() {
-+ return (LinkedNode<E>)TAIL_HANDLE.get(this);
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ protected final LinkedNode<E> getTailOpaque() {
-+ return (LinkedNode<E>)TAIL_HANDLE.getOpaque(this);
-+ }
-+
-+ /**
-+ * Constructs a {@code MultiThreadedQueue}, initially empty.
-+ * <p>
-+ * The returned object may not be published without synchronization.
-+ * </p>
-+ */
-+ public MultiThreadedQueue() {
-+ final LinkedNode<E> value = new LinkedNode<>(null, null);
-+ this.setHeadPlain(value);
-+ this.setTailPlain(value);
-+ }
-+
-+ /**
-+ * Constructs a {@code MultiThreadedQueue}, initially containing all elements in the specified {@code collection}.
-+ * <p>
-+ * The returned object may not be published without synchronization.
-+ * </p>
-+ * @param collection The specified collection.
-+ * @throws NullPointerException If {@code collection} is {@code null} or contains {@code null} elements.
-+ */
-+ public MultiThreadedQueue(final Iterable<? extends E> collection) {
-+ final Iterator<? extends E> elements = collection.iterator();
-+
-+ if (!elements.hasNext()) {
-+ final LinkedNode<E> value = new LinkedNode<>(null, null);
-+ this.setHeadPlain(value);
-+ this.setTailPlain(value);
-+ return;
-+ }
-+
-+ final LinkedNode<E> head = new LinkedNode<>(Validate.notNull(elements.next(), "Null element"), null);
-+ LinkedNode<E> tail = head;
-+
-+ while (elements.hasNext()) {
-+ final LinkedNode<E> next = new LinkedNode<>(Validate.notNull(elements.next(), "Null element"), null);
-+ tail.setNextPlain(next);
-+ tail = next;
-+ }
-+
-+ this.setHeadPlain(head);
-+ this.setTailPlain(tail);
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public E remove() throws NoSuchElementException {
-+ final E ret = this.poll();
-+
-+ if (ret == null) {
-+ throw new NoSuchElementException();
-+ }
-+
-+ return ret;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ * <p>
-+ * Contrary to the specification of {@link Collection#add}, this method will fail to add the element to this queue
-+ * and return {@code false} if this queue is add-blocked.
-+ * </p>
-+ */
-+ @Override
-+ public boolean add(final E element) {
-+ return this.offer(element);
-+ }
-+
-+ /**
-+ * Adds the specified element to the tail of this queue. If this queue is currently add-locked, then the queue is
-+ * released from that lock and this element is added. The unlock operation and addition of the specified
-+ * element is atomic.
-+ * @param element The specified element.
-+ * @return {@code true} if this queue previously allowed additions
-+ */
-+ public boolean forceAdd(final E element) {
-+ final LinkedNode<E> node = new LinkedNode<>(element, null);
-+
-+ return !this.forceAppendList(node, node);
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public E element() throws NoSuchElementException {
-+ final E ret = this.peek();
-+
-+ if (ret == null) {
-+ throw new NoSuchElementException();
-+ }
-+
-+ return ret;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ * <p>
-+ * This method may also return {@code false} to indicate an element was not added if this queue is add-blocked.
-+ * </p>
-+ */
-+ @Override
-+ public boolean offer(final E element) {
-+ Validate.notNull(element, "Null element");
-+
-+ final LinkedNode<E> node = new LinkedNode<>(element, null);
-+
-+ return this.appendList(node, node);
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public E peek() {
-+ for (LinkedNode<E> head = this.getHeadOpaque(), curr = head;;) {
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+ final E element = curr.getElementPlain(); /* Likely in sync */
-+
-+ if (element != null) {
-+ if (this.getHeadOpaque() == head && curr != head) {
-+ this.setHeadOpaque(curr);
-+ }
-+ return element;
-+ }
-+
-+ if (next == null || curr == next) {
-+ return null;
-+ }
-+ curr = next;
-+ }
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public E poll() {
-+ return this.removeHead();
-+ }
-+
-+ /**
-+ * Retrieves and removes the head of this queue if it matches the specified predicate. If this queue is empty
-+ * or the head does not match the predicate, this function returns {@code null}.
-+ * <p>
-+ * The predicate may be invoked multiple or no times in this call.
-+ * </p>
-+ * @param predicate The specified predicate.
-+ * @return The head if it matches the predicate, or {@code null} if it did not or this queue is empty.
-+ */
-+ public E pollIf(final Predicate<E> predicate) {
-+ return this.removeHead(Validate.notNull(predicate, "Null predicate"));
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public void clear() {
-+ //noinspection StatementWithEmptyBody
-+ while (this.poll() != null);
-+ }
-+
-+ /**
-+ * Prevents elements from being added to this queue. Once this is called, any attempt to add to this queue will fail.
-+ * <p>
-+ * This function is MT-Safe.
-+ * </p>
-+ * @return {@code true} if the queue was modified to prevent additions, {@code false} if it already prevented additions.
-+ */
-+ public boolean preventAdds() {
-+ final LinkedNode<E> deadEnd = new LinkedNode<>(null, null);
-+ deadEnd.setNextPlain(deadEnd);
-+
-+ if (!this.appendList(deadEnd, deadEnd)) {
-+ return false;
-+ }
-+
-+ this.setTailPlain(deadEnd); /* (try to) Ensure tail is set for the following #allowAdds call */
-+ return true;
-+ }
-+
-+ /**
-+ * Allows elements to be added to this queue once again. Note that this function has undefined behaviour if
-+ * {@link #preventAdds()} is not called beforehand. The benefit of this function over {@link #tryAllowAdds()}
-+ * is that this function might perform better.
-+ * <p>
-+ * This function is not MT-Safe.
-+ * </p>
-+ */
-+ public void allowAdds() {
-+ LinkedNode<E> tail = this.getTailPlain();
-+
-+ /* We need to find the tail given the cas on tail isn't atomic (nor volatile) in this.appendList */
-+ /* Thus it is possible for an outdated tail to be set */
-+ while (tail != (tail = tail.getNextPlain())) {}
-+
-+ tail.setNextVolatile(null);
-+ }
-+
-+ /**
-+ * Tries to allow elements to be added to this queue. Returns {@code true} if the queue was previous add-locked,
-+ * {@code false} otherwise.
-+ * <p>
-+ * This function is MT-Safe, however it should not be used with {@link #allowAdds()}.
-+ * </p>
-+ * @return {@code true} if the queue was previously add-locked, {@code false} otherwise.
-+ */
-+ public boolean tryAllowAdds() {
-+ LinkedNode<E> tail = this.getTailPlain();
-+
-+ for (int failures = 0;;) {
-+ /* We need to find the tail given the cas on tail isn't atomic (nor volatile) in this.appendList */
-+ /* Thus it is possible for an outdated tail to be set */
-+ while (tail != (tail = tail.getNextAcquire())) {
-+ if (tail == null) {
-+ return false;
-+ }
-+ }
-+
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (tail == (tail = tail.compareExchangeNextVolatile(tail, null))) {
-+ return true;
-+ }
-+
-+ if (tail == null) {
-+ return false;
-+ }
-+ ++failures;
-+ }
-+ }
-+
-+ /**
-+ * Atomically adds the specified element to this queue or allows additions to the queue. If additions
-+ * are not allowed, the element is not added.
-+ * <p>
-+ * This function is MT-Safe.
-+ * </p>
-+ * @param element The specified element.
-+ * @return {@code true} if the queue now allows additions, {@code false} if the element was added.
-+ */
-+ public boolean addOrAllowAdds(final E element) {
-+ Validate.notNull(element, "Null element");
-+ int failures = 0;
-+
-+ final LinkedNode<E> append = new LinkedNode<>(element, null);
-+
-+ for (LinkedNode<E> currTail = this.getTailOpaque(), curr = currTail;;) {
-+ /* It has been experimentally shown that placing the read before the backoff results in significantly greater performance */
-+ /* It is likely due to a cache miss caused by another write to the next field */
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (next == null) {
-+ final LinkedNode<E> compared = curr.compareExchangeNextVolatile(null, append);
-+
-+ if (compared == null) {
-+ /* Added */
-+ /* Avoid CASing on tail more than we need to */
-+ /* CAS to avoid setting an out-of-date tail */
-+ if (this.getTailOpaque() == currTail) {
-+ this.setTailOpaque(append);
-+ }
-+ return false; // we added
-+ }
-+
-+ ++failures;
-+ curr = compared;
-+ continue;
-+ } else if (next == curr) {
-+ final LinkedNode<E> compared = curr.compareExchangeNextVolatile(curr, null);
-+
-+ if (compared == curr) {
-+ return true; // we let additions through
-+ }
-+
-+ ++failures;
-+
-+ if (compared != null) {
-+ curr = compared;
-+ }
-+ continue;
-+ }
-+
-+ if (curr == currTail) {
-+ /* Tail is likely not up-to-date */
-+ curr = next;
-+ } else {
-+ /* Try to update to tail */
-+ if (currTail == (currTail = this.getTailOpaque())) {
-+ curr = next;
-+ } else {
-+ curr = currTail;
-+ }
-+ }
-+ }
-+ }
-+
-+ /**
-+ * 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>
-+ * This function is MT-Safe.
-+ * </p>
-+ * If the queue is already add-blocked and empty then no operation is performed.
-+ * @return {@code null} if the queue is now add-blocked or was previously add-blocked, else returns
-+ * an non-null value which was the previous head of queue.
-+ */
-+ public E pollOrBlockAdds() {
-+ int failures = 0;
-+ for (LinkedNode<E> head = this.getHeadOpaque(), curr = head;;) {
-+ final E currentVal = curr.getElementVolatile();
-+ final LinkedNode<E> next = curr.getNextOpaque();
-+
-+ if (next == curr) {
-+ return null; /* Additions are already blocked */
-+ }
-+
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (currentVal != null) {
-+ if (curr.getAndSetElementVolatile(null) == null) {
-+ ++failures;
-+ continue;
-+ }
-+
-+ /* "CAS" to avoid setting an out-of-date head */
-+ if (this.getHeadOpaque() == head) {
-+ this.setHeadOpaque(next != null ? next : curr);
-+ }
-+
-+ return currentVal;
-+ }
-+
-+ if (next == null) {
-+ /* Try to update stale head */
-+ if (curr != head && this.getHeadOpaque() == head) {
-+ this.setHeadOpaque(curr);
-+ }
-+
-+ final LinkedNode<E> compared = curr.compareExchangeNextVolatile(null, curr);
-+
-+ if (compared != null) {
-+ // failed to block additions
-+ curr = compared;
-+ ++failures;
-+ continue;
-+ }
-+
-+ return null; /* We blocked additions */
-+ }
-+
-+ if (head == curr) {
-+ /* head is likely not up-to-date */
-+ curr = next;
-+ } else {
-+ /* Try to update to head */
-+ if (head == (head = this.getHeadOpaque())) {
-+ curr = next;
-+ } else {
-+ curr = head;
-+ }
-+ }
-+ }
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public boolean remove(final Object object) {
-+ Validate.notNull(object, "Null object to remove");
-+
-+ for (LinkedNode<E> curr = this.getHeadOpaque();;) {
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+ final E element = curr.getElementPlain(); /* Likely in sync */
-+
-+ if (element != null) {
-+ if ((element == object || element.equals(object)) && curr.getAndSetElementVolatile(null) == element) {
-+ return true;
-+ }
-+ }
-+
-+ if (next == curr || next == null) {
-+ break;
-+ }
-+ curr = next;
-+ }
-+
-+ return false;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public boolean removeIf(final Predicate<? super E> filter) {
-+ Validate.notNull(filter, "Null filter");
-+
-+ boolean ret = false;
-+
-+ for (LinkedNode<E> curr = this.getHeadOpaque();;) {
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+ final E element = curr.getElementPlain(); /* Likely in sync */
-+
-+ if (element != null) {
-+ ret |= filter.test(element) && curr.getAndSetElementVolatile(null) == element;
-+ }
-+
-+ if (next == null || next == curr) {
-+ break;
-+ }
-+ curr = next;
-+ }
-+
-+ return ret;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public boolean removeAll(final Collection<?> collection) {
-+ Validate.notNull(collection, "Null collection");
-+
-+ boolean ret = false;
-+
-+ /* Volatile is required to synchronize with the write to the first element */
-+ for (LinkedNode<E> curr = this.getHeadOpaque();;) {
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+ final E element = curr.getElementPlain(); /* Likely in sync */
-+
-+ if (element != null) {
-+ ret |= collection.contains(element) && curr.getAndSetElementVolatile(null) == element;
-+ }
-+
-+ if (next == null || next == curr) {
-+ break;
-+ }
-+ curr = next;
-+ }
-+
-+ return ret;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public boolean retainAll(final Collection<?> collection) {
-+ Validate.notNull(collection, "Null collection");
-+
-+ boolean ret = false;
-+
-+ for (LinkedNode<E> curr = this.getHeadOpaque();;) {
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+ final E element = curr.getElementPlain(); /* Likely in sync */
-+
-+ if (element != null) {
-+ ret |= !collection.contains(element) && curr.getAndSetElementVolatile(null) == element;
-+ }
-+
-+ if (next == null || next == curr) {
-+ break;
-+ }
-+ curr = next;
-+ }
-+
-+ return ret;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public Object[] toArray() {
-+ final List<E> ret = new ArrayList<>();
-+
-+ for (LinkedNode<E> curr = this.getHeadOpaque();;) {
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+ final E element = curr.getElementPlain(); /* Likely in sync */
-+
-+ if (element != null) {
-+ ret.add(element);
-+ }
-+
-+ if (next == null || next == curr) {
-+ break;
-+ }
-+ curr = next;
-+ }
-+
-+ return ret.toArray();
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public <T> T[] toArray(final T[] array) {
-+ final List<T> ret = new ArrayList<>();
-+
-+ for (LinkedNode<E> curr = this.getHeadOpaque();;) {
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+ final E element = curr.getElementPlain(); /* Likely in sync */
-+
-+ if (element != null) {
-+ //noinspection unchecked
-+ ret.add((T)element);
-+ }
-+
-+ if (next == null || next == curr) {
-+ break;
-+ }
-+ curr = next;
-+ }
-+
-+ return ret.toArray(array);
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public <T> T[] toArray(final IntFunction<T[]> generator) {
-+ Validate.notNull(generator, "Null generator");
-+
-+ final List<T> ret = new ArrayList<>();
-+
-+ for (LinkedNode<E> curr = this.getHeadOpaque();;) {
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+ final E element = curr.getElementPlain(); /* Likely in sync */
-+
-+ if (element != null) {
-+ //noinspection unchecked
-+ ret.add((T)element);
-+ }
-+
-+ if (next == null || next == curr) {
-+ break;
-+ }
-+ curr = next;
-+ }
-+
-+ return ret.toArray(generator);
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public String toString() {
-+ final StringBuilder builder = new StringBuilder();
-+
-+ builder.append("MultiThreadedQueue: {elements: {");
-+
-+ int deadEntries = 0;
-+ int totalEntries = 0;
-+ int aliveEntries = 0;
-+
-+ boolean addLocked = false;
-+
-+ for (LinkedNode<E> curr = this.getHeadOpaque();; ++totalEntries) {
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+ final E element = curr.getElementPlain(); /* Likely in sync */
-+
-+ if (element == null) {
-+ ++deadEntries;
-+ } else {
-+ ++aliveEntries;
-+ }
-+
-+ if (totalEntries != 0) {
-+ builder.append(", ");
-+ }
-+
-+ builder.append(totalEntries).append(": \"").append(element).append('"');
-+
-+ if (next == null) {
-+ break;
-+ }
-+ if (curr == next) {
-+ addLocked = true;
-+ break;
-+ }
-+ curr = next;
-+ }
-+
-+ builder.append("}, total_entries: \"").append(totalEntries).append("\", alive_entries: \"").append(aliveEntries)
-+ .append("\", dead_entries:").append(deadEntries).append("\", add_locked: \"").append(addLocked)
-+ .append("\"}");
-+
-+ return builder.toString();
-+ }
-+
-+ /**
-+ * Adds all elements from the specified collection to this queue. The addition is atomic.
-+ * @param collection The specified collection.
-+ * @return {@code true} if all elements were added successfully, or {@code false} if this queue is add-blocked, or
-+ * {@code false} if the specified collection contains no elements.
-+ */
-+ @Override
-+ public boolean addAll(final Collection<? extends E> collection) {
-+ return this.addAll((Iterable<? extends E>)collection);
-+ }
-+
-+ /**
-+ * Adds all elements from the specified iterable object to this queue. The addition is atomic.
-+ * @param iterable The specified iterable object.
-+ * @return {@code true} if all elements were added successfully, or {@code false} if this queue is add-blocked, or
-+ * {@code false} if the specified iterable contains no elements.
-+ */
-+ public boolean addAll(final Iterable<? extends E> iterable) {
-+ Validate.notNull(iterable, "Null iterable");
-+
-+ final Iterator<? extends E> elements = iterable.iterator();
-+ if (!elements.hasNext()) {
-+ return false;
-+ }
-+
-+ /* Build a list of nodes to append */
-+ /* This is an much faster due to the fact that zero additional synchronization is performed */
-+
-+ final LinkedNode<E> head = new LinkedNode<>(Validate.notNull(elements.next(), "Null element"), null);
-+ LinkedNode<E> tail = head;
-+
-+ while (elements.hasNext()) {
-+ final LinkedNode<E> next = new LinkedNode<>(Validate.notNull(elements.next(), "Null element"), null);
-+ tail.setNextPlain(next);
-+ tail = next;
-+ }
-+
-+ return this.appendList(head, tail);
-+ }
-+
-+ /**
-+ * Adds all of the elements from the specified array to this queue.
-+ * @param items The specified array.
-+ * @return {@code true} if all elements were added successfully, or {@code false} if this queue is add-blocked, or
-+ * {@code false} if the specified array has a length of 0.
-+ */
-+ public boolean addAll(final E[] items) {
-+ return this.addAll(items, 0, items.length);
-+ }
-+
-+ /**
-+ * Adds all of the elements from the specified array to this queue.
-+ * @param items The specified array.
-+ * @param off The offset in the array.
-+ * @param len The number of items.
-+ * @return {@code true} if all elements were added successfully, or {@code false} if this queue is add-blocked, or
-+ * {@code false} if the specified array has a length of 0.
-+ */
-+ public boolean addAll(final E[] items, final int off, final int len) {
-+ Validate.notNull(items, "Items may not be null");
-+ Validate.arrayBounds(off, len, items.length, "Items array indices out of bounds");
-+
-+ if (len == 0) {
-+ return false;
-+ }
-+
-+ final LinkedNode<E> head = new LinkedNode<>(Validate.notNull(items[off], "Null element"), null);
-+ LinkedNode<E> tail = head;
-+
-+ for (int i = 1; i < len; ++i) {
-+ final LinkedNode<E> next = new LinkedNode<>(Validate.notNull(items[off + i], "Null element"), null);
-+ tail.setNextPlain(next);
-+ tail = next;
-+ }
-+
-+ return this.appendList(head, tail);
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public boolean containsAll(final Collection<?> collection) {
-+ Validate.notNull(collection, "Null collection");
-+
-+ for (final Object element : collection) {
-+ if (!this.contains(element)) {
-+ return false;
-+ }
-+ }
-+ return false;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public Iterator<E> iterator() {
-+ return new LinkedIterator<>(this.getHeadOpaque());
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ * <p>
-+ * Note that this function is computed non-atomically and in O(n) time. The value returned may not be representative of
-+ * the queue in its current state.
-+ * </p>
-+ */
-+ @Override
-+ public int size() {
-+ int size = 0;
-+
-+ /* Volatile is required to synchronize with the write to the first element */
-+ for (LinkedNode<E> curr = this.getHeadOpaque();;) {
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+ final E element = curr.getElementPlain(); /* Likely in sync */
-+
-+ if (element != null) {
-+ ++size;
-+ }
-+
-+ if (next == null || next == curr) {
-+ break;
-+ }
-+ curr = next;
-+ }
-+
-+ return size;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public boolean isEmpty() {
-+ return this.peek() == null;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public boolean contains(final Object object) {
-+ Validate.notNull(object, "Null object");
-+
-+ for (LinkedNode<E> curr = this.getHeadOpaque();;) {
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+ final E element = curr.getElementPlain(); /* Likely in sync */
-+
-+ if (element != null && (element == object || element.equals(object))) {
-+ return true;
-+ }
-+
-+ if (next == null || next == curr) {
-+ break;
-+ }
-+ curr = next;
-+ }
-+
-+ return false;
-+ }
-+
-+ /**
-+ * Finds the first element in this queue that matches the predicate.
-+ * @param predicate The predicate to test elements against.
-+ * @return The first element that matched the predicate, {@code null} if none matched.
-+ */
-+ public E find(final Predicate<E> predicate) {
-+ Validate.notNull(predicate, "Null predicate");
-+
-+ for (LinkedNode<E> curr = this.getHeadOpaque();;) {
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+ final E element = curr.getElementPlain(); /* Likely in sync */
-+
-+ if (element != null && predicate.test(element)) {
-+ return element;
-+ }
-+
-+ if (next == null || next == curr) {
-+ break;
-+ }
-+ curr = next;
-+ }
-+
-+ return null;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public void forEach(final Consumer<? super E> action) {
-+ Validate.notNull(action, "Null action");
-+
-+ for (LinkedNode<E> curr = this.getHeadOpaque();;) {
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+ final E element = curr.getElementPlain(); /* Likely in sync */
-+
-+ if (element != null) {
-+ action.accept(element);
-+ }
-+
-+ if (next == null || next == curr) {
-+ break;
-+ }
-+ curr = next;
-+ }
-+ }
-+
-+ // return true if normal addition, false if the queue previously disallowed additions
-+ protected final boolean forceAppendList(final LinkedNode<E> head, final LinkedNode<E> tail) {
-+ int failures = 0;
-+
-+ for (LinkedNode<E> currTail = this.getTailOpaque(), curr = currTail;;) {
-+ /* It has been experimentally shown that placing the read before the backoff results in significantly greater performance */
-+ /* It is likely due to a cache miss caused by another write to the next field */
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (next == null || next == curr) {
-+ final LinkedNode<E> compared = curr.compareExchangeNextVolatile(next, head);
-+
-+ if (compared == next) {
-+ /* Added */
-+ /* Avoid CASing on tail more than we need to */
-+ /* "CAS" to avoid setting an out-of-date tail */
-+ if (this.getTailOpaque() == currTail) {
-+ this.setTailOpaque(tail);
-+ }
-+ return next != curr;
-+ }
-+
-+ ++failures;
-+ curr = compared;
-+ continue;
-+ }
-+
-+ if (curr == currTail) {
-+ /* Tail is likely not up-to-date */
-+ curr = next;
-+ } else {
-+ /* Try to update to tail */
-+ if (currTail == (currTail = this.getTailOpaque())) {
-+ curr = next;
-+ } else {
-+ curr = currTail;
-+ }
-+ }
-+ }
-+ }
-+
-+ // return true if successful, false otherwise
-+ protected final boolean appendList(final LinkedNode<E> head, final LinkedNode<E> tail) {
-+ int failures = 0;
-+
-+ for (LinkedNode<E> currTail = this.getTailOpaque(), curr = currTail;;) {
-+ /* It has been experimentally shown that placing the read before the backoff results in significantly greater performance */
-+ /* It is likely due to a cache miss caused by another write to the next field */
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+
-+ if (next == curr) {
-+ /* Additions are stopped */
-+ return false;
-+ }
-+
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (next == null) {
-+ final LinkedNode<E> compared = curr.compareExchangeNextVolatile(null, head);
-+
-+ if (compared == null) {
-+ /* Added */
-+ /* Avoid CASing on tail more than we need to */
-+ /* CAS to avoid setting an out-of-date tail */
-+ if (this.getTailOpaque() == currTail) {
-+ this.setTailOpaque(tail);
-+ }
-+ return true;
-+ }
-+
-+ ++failures;
-+ curr = compared;
-+ continue;
-+ }
-+
-+ if (curr == currTail) {
-+ /* Tail is likely not up-to-date */
-+ curr = next;
-+ } else {
-+ /* Try to update to tail */
-+ if (currTail == (currTail = this.getTailOpaque())) {
-+ curr = next;
-+ } else {
-+ curr = currTail;
-+ }
-+ }
-+ }
-+ }
-+
-+ protected final E removeHead(final Predicate<E> predicate) {
-+ int failures = 0;
-+ for (LinkedNode<E> head = this.getHeadOpaque(), curr = head;;) {
-+ // volatile here synchronizes-with writes to element
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+ final E currentVal = curr.getElementPlain();
-+
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (currentVal != null) {
-+ if (!predicate.test(currentVal)) {
-+ /* Try to update stale head */
-+ if (curr != head && this.getHeadOpaque() == head) {
-+ this.setHeadOpaque(curr);
-+ }
-+ return null;
-+ }
-+ if (curr.getAndSetElementVolatile(null) == null) {
-+ /* Failed to get head */
-+ if (curr == (curr = next) || next == null) {
-+ return null;
-+ }
-+ ++failures;
-+ continue;
-+ }
-+
-+ /* "CAS" to avoid setting an out-of-date head */
-+ if (this.getHeadOpaque() == head) {
-+ this.setHeadOpaque(next != null ? next : curr);
-+ }
-+
-+ return currentVal;
-+ }
-+
-+ if (curr == next || next == null) {
-+ /* Try to update stale head */
-+ if (curr != head && this.getHeadOpaque() == head) {
-+ this.setHeadOpaque(curr);
-+ }
-+ return null; /* End of queue */
-+ }
-+
-+ if (head == curr) {
-+ /* head is likely not up-to-date */
-+ curr = next;
-+ } else {
-+ /* Try to update to head */
-+ if (head == (head = this.getHeadOpaque())) {
-+ curr = next;
-+ } else {
-+ curr = head;
-+ }
-+ }
-+ }
-+ }
-+
-+ protected final E removeHead() {
-+ int failures = 0;
-+ for (LinkedNode<E> head = this.getHeadOpaque(), curr = head;;) {
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+ final E currentVal = curr.getElementPlain();
-+
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (currentVal != null) {
-+ if (curr.getAndSetElementVolatile(null) == null) {
-+ /* Failed to get head */
-+ if (curr == (curr = next) || next == null) {
-+ return null;
-+ }
-+ ++failures;
-+ continue;
-+ }
-+
-+ /* "CAS" to avoid setting an out-of-date head */
-+ if (this.getHeadOpaque() == head) {
-+ this.setHeadOpaque(next != null ? next : curr);
-+ }
-+
-+ return currentVal;
-+ }
-+
-+ if (curr == next || next == null) {
-+ /* Try to update stale head */
-+ if (curr != head && this.getHeadOpaque() == head) {
-+ this.setHeadOpaque(curr);
-+ }
-+ return null; /* End of queue */
-+ }
-+
-+ if (head == curr) {
-+ /* head is likely not up-to-date */
-+ curr = next;
-+ } else {
-+ /* Try to update to head */
-+ if (head == (head = this.getHeadOpaque())) {
-+ curr = next;
-+ } else {
-+ curr = head;
-+ }
-+ }
-+ }
-+ }
-+
-+ /**
-+ * Empties the queue into the specified consumer. This function is optimized for single-threaded reads, and should
-+ * be faster than a loop on {@link #poll()}.
-+ * <p>
-+ * This function is not MT-Safe. This function cannot be called with other read operations ({@link #peek()}, {@link #poll()},
-+ * {@link #clear()}, etc).
-+ * Write operations are safe to be called concurrently.
-+ * </p>
-+ * @param consumer The consumer to accept the elements.
-+ * @return The total number of elements drained.
-+ */
-+ public int drain(final Consumer<E> consumer) {
-+ return this.drain(consumer, false, ConcurrentUtil::rethrow);
-+ }
-+
-+ /**
-+ * Empties the queue into the specified consumer. This function is optimized for single-threaded reads, and should
-+ * be faster than a loop on {@link #poll()}.
-+ * <p>
-+ * If {@code preventAdds} is {@code true}, then after this function returns the queue is guaranteed to be empty and
-+ * additions to the queue will fail.
-+ * </p>
-+ * <p>
-+ * This function is not MT-Safe. This function cannot be called with other read operations ({@link #peek()}, {@link #poll()},
-+ * {@link #clear()}, etc).
-+ * Write operations are safe to be called concurrently.
-+ * </p>
-+ * @param consumer The consumer to accept the elements.
-+ * @param preventAdds Whether to prevent additions to this queue after draining.
-+ * @return The total number of elements drained.
-+ */
-+ public int drain(final Consumer<E> consumer, final boolean preventAdds) {
-+ return this.drain(consumer, preventAdds, ConcurrentUtil::rethrow);
-+ }
-+
-+ /**
-+ * Empties the queue into the specified consumer. This function is optimized for single-threaded reads, and should
-+ * be faster than a loop on {@link #poll()}.
-+ * <p>
-+ * If {@code preventAdds} is {@code true}, then after this function returns the queue is guaranteed to be empty and
-+ * additions to the queue will fail.
-+ * </p>
-+ * <p>
-+ * This function is not MT-Safe. This function cannot be called with other read operations ({@link #peek()}, {@link #poll()},
-+ * {@link #clear()}, {@link #remove(Object)} etc).
-+ * Only write operations are safe to be called concurrently.
-+ * </p>
-+ * @param consumer The consumer to accept the elements.
-+ * @param preventAdds Whether to prevent additions to this queue after draining.
-+ * @param exceptionHandler Invoked when the consumer raises an exception.
-+ * @return The total number of elements drained.
-+ */
-+ public int drain(final Consumer<E> consumer, final boolean preventAdds, final Consumer<Throwable> exceptionHandler) {
-+ Validate.notNull(consumer, "Null consumer");
-+ Validate.notNull(exceptionHandler, "Null exception handler");
-+
-+ /* This function assumes proper synchronization is made to ensure drain and no other read function are called concurrently */
-+ /* This allows plain write usages instead of opaque or higher */
-+ int total = 0;
-+
-+ final LinkedNode<E> head = this.getHeadAcquire(); /* Required to synchronize with the write to the first element field */
-+ LinkedNode<E> curr = head;
-+
-+ for (;;) {
-+ /* Volatile acquires with the write to the element field */
-+ final E currentVal = curr.getElementPlain();
-+ LinkedNode<E> next = curr.getNextVolatile();
-+
-+ if (next == curr) {
-+ /* Add-locked nodes always have a null value */
-+ break;
-+ }
-+
-+ if (currentVal == null) {
-+ if (next == null) {
-+ if (preventAdds && (next = curr.compareExchangeNextVolatile(null, curr)) != null) {
-+ // failed to prevent adds, continue
-+ curr = next;
-+ continue;
-+ } else {
-+ // we're done here
-+ break;
-+ }
-+ }
-+ curr = next;
-+ continue;
-+ }
-+
-+ try {
-+ consumer.accept(currentVal);
-+ } catch (final Exception ex) {
-+ this.setHeadOpaque(next != null ? next : curr); /* Avoid perf penalty (of reiterating) if the exception handler decides to re-throw */
-+ curr.setElementOpaque(null); /* set here, we might re-throw */
-+
-+ exceptionHandler.accept(ex);
-+ }
-+
-+ curr.setElementOpaque(null);
-+
-+ ++total;
-+
-+ if (next == null) {
-+ if (preventAdds && (next = curr.compareExchangeNextVolatile(null, curr)) != null) {
-+ /* Retry with next value */
-+ curr = next;
-+ continue;
-+ }
-+ break;
-+ }
-+
-+ curr = next;
-+ }
-+ if (curr != head) {
-+ this.setHeadOpaque(curr); /* While this may be a plain write, eventually publish it for methods such as find. */
-+ }
-+ return total;
-+ }
-+
-+ @Override
-+ public Spliterator<E> spliterator() { // TODO implement
-+ return Spliterators.spliterator(this, Spliterator.CONCURRENT |
-+ Spliterator.NONNULL | Spliterator.ORDERED);
-+ }
-+
-+ protected static final class LinkedNode<E> {
-+
-+ protected volatile Object element;
-+ protected volatile LinkedNode<E> next;
-+
-+ protected static final VarHandle ELEMENT_HANDLE = ConcurrentUtil.getVarHandle(LinkedNode.class, "element", Object.class);
-+ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(LinkedNode.class, "next", LinkedNode.class);
-+
-+ protected LinkedNode(final Object element, final LinkedNode<E> next) {
-+ ELEMENT_HANDLE.set(this, element);
-+ NEXT_HANDLE.set(this, next);
-+ }
-+
-+ /* element */
-+
-+ @SuppressWarnings("unchecked")
-+ protected final E getElementPlain() {
-+ return (E)ELEMENT_HANDLE.get(this);
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ protected final E getElementVolatile() {
-+ return (E)ELEMENT_HANDLE.getVolatile(this);
-+ }
-+
-+ protected final void setElementPlain(final E update) {
-+ ELEMENT_HANDLE.set(this, (Object)update);
-+ }
-+
-+ protected final void setElementOpaque(final E update) {
-+ ELEMENT_HANDLE.setOpaque(this, (Object)update);
-+ }
-+
-+ protected final void setElementVolatile(final E update) {
-+ ELEMENT_HANDLE.setVolatile(this, (Object)update);
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ protected final E getAndSetElementVolatile(final E update) {
-+ return (E)ELEMENT_HANDLE.getAndSet(this, update);
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ protected final E compareExchangeElementVolatile(final E expect, final E update) {
-+ return (E)ELEMENT_HANDLE.compareAndExchange(this, expect, update);
-+ }
-+
-+ /* next */
-+
-+ @SuppressWarnings("unchecked")
-+ protected final LinkedNode<E> getNextPlain() {
-+ return (LinkedNode<E>)NEXT_HANDLE.get(this);
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ protected final LinkedNode<E> getNextOpaque() {
-+ return (LinkedNode<E>)NEXT_HANDLE.getOpaque(this);
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ protected final LinkedNode<E> getNextAcquire() {
-+ return (LinkedNode<E>)NEXT_HANDLE.getAcquire(this);
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ protected final LinkedNode<E> getNextVolatile() {
-+ return (LinkedNode<E>)NEXT_HANDLE.getVolatile(this);
-+ }
-+
-+ protected final void setNextPlain(final LinkedNode<E> next) {
-+ NEXT_HANDLE.set(this, next);
-+ }
-+
-+ protected final void setNextVolatile(final LinkedNode<E> next) {
-+ NEXT_HANDLE.setVolatile(this, next);
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ protected final LinkedNode<E> compareExchangeNextVolatile(final LinkedNode<E> expect, final LinkedNode<E> set) {
-+ return (LinkedNode<E>)NEXT_HANDLE.compareAndExchange(this, expect, set);
-+ }
-+ }
-+
-+ protected static final class LinkedIterator<E> implements Iterator<E> {
-+
-+ protected LinkedNode<E> curr; /* last returned by next() */
-+ protected LinkedNode<E> next; /* next to return from next() */
-+ protected E nextElement; /* cached to avoid a race condition with removing or polling */
-+
-+ protected LinkedIterator(final LinkedNode<E> start) {
-+ /* setup nextElement and next */
-+ for (LinkedNode<E> curr = start;;) {
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+
-+ final E element = curr.getElementPlain();
-+
-+ if (element != null) {
-+ this.nextElement = element;
-+ this.next = curr;
-+ break;
-+ }
-+
-+ if (next == null || next == curr) {
-+ break;
-+ }
-+ curr = next;
-+ }
-+ }
-+
-+ protected final void findNext() {
-+ /* only called if this.nextElement != null, which means this.next != null */
-+ for (LinkedNode<E> curr = this.next;;) {
-+ final LinkedNode<E> next = curr.getNextVolatile();
-+
-+ if (next == null || next == curr) {
-+ break;
-+ }
-+
-+ final E element = next.getElementPlain();
-+
-+ if (element != null) {
-+ this.nextElement = element;
-+ this.curr = this.next; /* this.next will be the value returned from next(), set this.curr for remove() */
-+ this.next = next;
-+ return;
-+ }
-+ curr = next;
-+ }
-+
-+ /* out of nodes to iterate */
-+ /* keep curr for remove() calls */
-+ this.next = null;
-+ this.nextElement = null;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public boolean hasNext() {
-+ return this.nextElement != null;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public E next() {
-+ final E element = this.nextElement;
-+
-+ if (element == null) {
-+ throw new NoSuchElementException();
-+ }
-+
-+ this.findNext();
-+
-+ return element;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public void remove() {
-+ if (this.curr == null) {
-+ throw new IllegalStateException();
-+ }
-+
-+ this.curr.setElementVolatile(null);
-+ this.curr = null;
-+ }
-+ }
-+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..6bad6f8ecc0944d2f406924c7de7e227ff1e70fa
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java
-@@ -0,0 +1,110 @@
-+package ca.spottedleaf.concurrentutil.completable;
-+
-+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
-+import ca.spottedleaf.concurrentutil.executor.Cancellable;
-+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
-+import org.slf4j.Logger;
-+import org.slf4j.LoggerFactory;
-+import java.util.function.BiConsumer;
-+
-+public final class CallbackCompletable<T> {
-+
-+ private static final Logger LOGGER = LoggerFactory.getLogger(CallbackCompletable.class);
-+
-+ private final MultiThreadedQueue<BiConsumer<T, Throwable>> waiters = new MultiThreadedQueue<>();
-+ private T result;
-+ private Throwable throwable;
-+ private volatile boolean completed;
-+
-+ public boolean isCompleted() {
-+ return this.completed;
-+ }
-+
-+ /**
-+ * Note: Can only use after calling {@link #addAsynchronousWaiter(BiConsumer)}, as this function performs zero
-+ * synchronisation
-+ */
-+ public T getResult() {
-+ return this.result;
-+ }
-+
-+ /**
-+ * Note: Can only use after calling {@link #addAsynchronousWaiter(BiConsumer)}, as this function performs zero
-+ * synchronisation
-+ */
-+ public Throwable getThrowable() {
-+ return this.throwable;
-+ }
-+
-+ /**
-+ * Adds a waiter that should only be completed asynchronously by the complete() calls. If complete()
-+ * has already been called, returns {@code null} and does not invoke the specified consumer.
-+ * @param consumer Consumer to be executed on completion
-+ * @throws NullPointerException If consumer is null
-+ * @return A cancellable which will control the execution of the specified consumer
-+ */
-+ public Cancellable addAsynchronousWaiter(final BiConsumer<T, Throwable> consumer) {
-+ if (this.waiters.add(consumer)) {
-+ return new CancellableImpl(consumer);
-+ }
-+ return null;
-+ }
-+
-+ private void completeAllWaiters(final T result, final Throwable throwable) {
-+ this.completed = true;
-+ BiConsumer<T, Throwable> waiter;
-+ while ((waiter = this.waiters.pollOrBlockAdds()) != null) {
-+ this.completeWaiter(waiter, result, throwable);
-+ }
-+ }
-+
-+ private void completeWaiter(final BiConsumer<T, Throwable> consumer, final T result, final Throwable throwable) {
-+ try {
-+ consumer.accept(result, throwable);
-+ } catch (final Throwable throwable2) {
-+ LOGGER.error("Failed to complete callback " + ConcurrentUtil.genericToString(consumer), throwable2);
-+ }
-+ }
-+
-+ /**
-+ * Adds a waiter that will be completed asynchronously by the complete() calls. If complete()
-+ * has already been called, then invokes the consumer synchronously with the completed result.
-+ * @param consumer Consumer to be executed on completion
-+ * @throws NullPointerException If consumer is null
-+ * @return A cancellable which will control the execution of the specified consumer
-+ */
-+ public Cancellable addWaiter(final BiConsumer<T, Throwable> consumer) {
-+ if (this.waiters.add(consumer)) {
-+ return new CancellableImpl(consumer);
-+ }
-+ this.completeWaiter(consumer, this.result, this.throwable);
-+ return new CancellableImpl(consumer);
-+ }
-+
-+ public void complete(final T result) {
-+ this.result = result;
-+ this.completeAllWaiters(result, null);
-+ }
-+
-+ public void completeWithThrowable(final Throwable throwable) {
-+ if (throwable == null) {
-+ throw new NullPointerException("Throwable cannot be null");
-+ }
-+ this.throwable = throwable;
-+ this.completeAllWaiters(null, throwable);
-+ }
-+
-+ private final class CancellableImpl implements Cancellable {
-+
-+ private final BiConsumer<T, Throwable> waiter;
-+
-+ private CancellableImpl(final BiConsumer<T, Throwable> waiter) {
-+ this.waiter = waiter;
-+ }
-+
-+ @Override
-+ public boolean cancel() {
-+ return CallbackCompletable.this.waiters.remove(this.waiter);
-+ }
-+ }
-+}
-\ No newline at end of file
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..365616439fa079017d648ed7f6ddf6950a691adf
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/completable/Completable.java
-@@ -0,0 +1,737 @@
-+package ca.spottedleaf.concurrentutil.completable;
-+
-+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
-+import ca.spottedleaf.concurrentutil.util.Validate;
-+import org.slf4j.Logger;
-+import org.slf4j.LoggerFactory;
-+import java.lang.invoke.VarHandle;
-+import java.util.concurrent.CompletableFuture;
-+import java.util.concurrent.CompletionException;
-+import java.util.concurrent.CompletionStage;
-+import java.util.concurrent.Executor;
-+import java.util.concurrent.ForkJoinPool;
-+import java.util.concurrent.locks.LockSupport;
-+import java.util.function.BiConsumer;
-+import java.util.function.BiFunction;
-+import java.util.function.Consumer;
-+import java.util.function.Function;
-+import java.util.function.Supplier;
-+
-+public final class Completable<T> {
-+
-+ private static final Logger LOGGER = LoggerFactory.getLogger(Completable.class);
-+ private static final Function<? super Throwable, ? extends Throwable> DEFAULT_EXCEPTION_HANDLER = (final Throwable thr) -> {
-+ LOGGER.error("Unhandled exception during Completable operation", thr);
-+ return thr;
-+ };
-+
-+ public static Executor getDefaultExecutor() {
-+ return ForkJoinPool.commonPool();
-+ }
-+
-+ private static final Transform<?, ?> COMPLETED_STACK = new Transform<>(null, null, null, null) {
-+ @Override
-+ public void run() {}
-+ };
-+ private volatile Transform<?, T> completeStack;
-+ private static final VarHandle COMPLETE_STACK_HANDLE = ConcurrentUtil.getVarHandle(Completable.class, "completeStack", Transform.class);
-+
-+ private static final Object NULL_MASK = new Object();
-+ private volatile Object result;
-+ private static final VarHandle RESULT_HANDLE = ConcurrentUtil.getVarHandle(Completable.class, "result", Object.class);
-+
-+ private Object getResultPlain() {
-+ return (Object)RESULT_HANDLE.get(this);
-+ }
-+
-+ private Object getResultVolatile() {
-+ return (Object)RESULT_HANDLE.getVolatile(this);
-+ }
-+
-+ private void pushStackOrRun(final Transform<?, T> push) {
-+ int failures = 0;
-+ for (Transform<?, T> curr = (Transform<?, T>)COMPLETE_STACK_HANDLE.getVolatile(this);;) {
-+ if (curr == COMPLETED_STACK) {
-+ push.execute();
-+ return;
-+ }
-+
-+ push.next = curr;
-+
-+ for (int i = 0; i < failures; ++i) {
-+ ConcurrentUtil.backoff();
-+ }
-+
-+ if (curr == (curr = (Transform<?, T>)COMPLETE_STACK_HANDLE.compareAndExchange(this, curr, push))) {
-+ return;
-+ }
-+ push.next = null;
-+ ++failures;
-+ }
-+ }
-+
-+ private void propagateStack() {
-+ Transform<?, T> topStack = (Transform<?, T>)COMPLETE_STACK_HANDLE.getAndSet(this, COMPLETED_STACK);
-+ while (topStack != null) {
-+ topStack.execute();
-+ topStack = topStack.next;
-+ }
-+ }
-+
-+ private static Object maskNull(final Object res) {
-+ return res == null ? NULL_MASK : res;
-+ }
-+
-+ private static Object unmaskNull(final Object res) {
-+ return res == NULL_MASK ? null : res;
-+ }
-+
-+ private static Executor checkExecutor(final Executor executor) {
-+ return Validate.notNull(executor, "Executor may not be null");
-+ }
-+
-+ public Completable() {}
-+
-+ private Completable(final Object complete) {
-+ COMPLETE_STACK_HANDLE.set(this, COMPLETED_STACK);
-+ RESULT_HANDLE.setRelease(this, complete);
-+ }
-+
-+ public static <T> Completable<T> completed(final T value) {
-+ return new Completable<>(maskNull(value));
-+ }
-+
-+ public static <T> Completable<T> failed(final Throwable ex) {
-+ Validate.notNull(ex, "Exception may not be null");
-+
-+ return new Completable<>(new ExceptionResult(ex));
-+ }
-+
-+ public static <T> Completable<T> supplied(final Supplier<T> supplier) {
-+ return supplied(supplier, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public static <T> Completable<T> supplied(final Supplier<T> supplier, final Function<? super Throwable, ? extends Throwable> exceptionHandler) {
-+ try {
-+ return completed(supplier.get());
-+ } catch (final Throwable throwable) {
-+ Throwable complete;
-+ try {
-+ complete = exceptionHandler.apply(throwable);
-+ } catch (final Throwable thr2) {
-+ throwable.addSuppressed(thr2);
-+ complete = throwable;
-+ }
-+ return failed(complete);
-+ }
-+ }
-+
-+ public static <T> Completable<T> suppliedAsync(final Supplier<T> supplier, final Executor executor) {
-+ return suppliedAsync(supplier, executor, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public static <T> Completable<T> suppliedAsync(final Supplier<T> supplier, final Executor executor, final Function<? super Throwable, ? extends Throwable> exceptionHandler) {
-+ final Completable<T> ret = new Completable<>();
-+
-+ class AsyncSuppliedCompletable implements Runnable, CompletableFuture.AsynchronousCompletionTask {
-+ @Override
-+ public void run() {
-+ try {
-+ ret.complete(supplier.get());
-+ } catch (final Throwable throwable) {
-+ Throwable complete;
-+ try {
-+ complete = exceptionHandler.apply(throwable);
-+ } catch (final Throwable thr2) {
-+ throwable.addSuppressed(thr2);
-+ complete = throwable;
-+ }
-+ ret.completeExceptionally(complete);
-+ }
-+ }
-+ }
-+
-+ try {
-+ executor.execute(new AsyncSuppliedCompletable());
-+ } catch (final Throwable throwable) {
-+ Throwable complete;
-+ try {
-+ complete = exceptionHandler.apply(throwable);
-+ } catch (final Throwable thr2) {
-+ throwable.addSuppressed(thr2);
-+ complete = throwable;
-+ }
-+ ret.completeExceptionally(complete);
-+ }
-+
-+ return ret;
-+ }
-+
-+ private boolean completeRaw(final Object value) {
-+ if ((Object)RESULT_HANDLE.getVolatile(this) != null || !(boolean)RESULT_HANDLE.compareAndSet(this, (Object)null, value)) {
-+ return false;
-+ }
-+
-+ this.propagateStack();
-+ return true;
-+ }
-+
-+ public boolean complete(final T result) {
-+ return this.completeRaw(maskNull(result));
-+ }
-+
-+ public boolean completeExceptionally(final Throwable exception) {
-+ Validate.notNull(exception, "Exception may not be null");
-+
-+ return this.completeRaw(new ExceptionResult(exception));
-+ }
-+
-+ public boolean isDone() {
-+ return this.getResultVolatile() != null;
-+ }
-+
-+ public boolean isNormallyComplete() {
-+ return this.getResultVolatile() != null && !(this.getResultVolatile() instanceof ExceptionResult);
-+ }
-+
-+ public boolean isExceptionallyComplete() {
-+ return this.getResultVolatile() instanceof ExceptionResult;
-+ }
-+
-+ public Throwable getException() {
-+ final Object res = this.getResultVolatile();
-+ if (res == null) {
-+ return null;
-+ }
-+
-+ if (!(res instanceof ExceptionResult exRes)) {
-+ throw new IllegalStateException("Not completed exceptionally");
-+ }
-+
-+ return exRes.ex;
-+ }
-+
-+ public T getNow(final T dfl) throws CompletionException {
-+ final Object res = this.getResultVolatile();
-+ if (res == null) {
-+ return dfl;
-+ }
-+
-+ if (res instanceof ExceptionResult exRes) {
-+ throw new CompletionException(exRes.ex);
-+ }
-+
-+ return (T)unmaskNull(res);
-+ }
-+
-+ public T join() throws CompletionException {
-+ if (this.isDone()) {
-+ return this.getNow(null);
-+ }
-+
-+ final UnparkTransform<T> unparkTransform = new UnparkTransform<>(this, Thread.currentThread());
-+
-+ this.pushStackOrRun(unparkTransform);
-+
-+ boolean interuptted = false;
-+ while (!unparkTransform.isReleasable()) {
-+ try {
-+ ForkJoinPool.managedBlock(unparkTransform);
-+ } catch (final InterruptedException ex) {
-+ interuptted = true;
-+ }
-+ }
-+
-+ if (interuptted) {
-+ Thread.currentThread().interrupt();
-+ }
-+
-+ return this.getNow(null);
-+ }
-+
-+ public CompletableFuture<T> toFuture() {
-+ final Object rawResult = this.getResultVolatile();
-+ if (rawResult != null) {
-+ if (rawResult instanceof ExceptionResult exRes) {
-+ return CompletableFuture.failedFuture(exRes.ex);
-+ } else {
-+ return CompletableFuture.completedFuture((T)unmaskNull(rawResult));
-+ }
-+ }
-+
-+ final CompletableFuture<T> ret = new CompletableFuture<>();
-+
-+ class ToFuture implements BiConsumer<T, Throwable> {
-+
-+ @Override
-+ public void accept(final T res, final Throwable ex) {
-+ if (ex != null) {
-+ ret.completeExceptionally(ex);
-+ } else {
-+ ret.complete(res);
-+ }
-+ }
-+ }
-+
-+ this.whenComplete(new ToFuture());
-+
-+ return ret;
-+ }
-+
-+ public static <T> Completable<T> fromFuture(final CompletionStage<T> stage) {
-+ final Completable<T> ret = new Completable<>();
-+
-+ class FromFuture implements BiConsumer<T, Throwable> {
-+ @Override
-+ public void accept(final T res, final Throwable ex) {
-+ if (ex != null) {
-+ ret.completeExceptionally(ex);
-+ } else {
-+ ret.complete(res);
-+ }
-+ }
-+ }
-+
-+ stage.whenComplete(new FromFuture());
-+
-+ return ret;
-+ }
-+
-+
-+ public <U> Completable<U> thenApply(final Function<? super T, ? extends U> function) {
-+ return this.thenApply(function, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public <U> Completable<U> thenApply(final Function<? super T, ? extends U> function, final Function<? super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(function, "Function may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable<U> ret = new Completable<>();
-+ this.pushStackOrRun(new ApplyTransform<>(null, this, ret, exceptionHandler, function));
-+ return ret;
-+ }
-+
-+ public <U> Completable<U> thenApplyAsync(final Function<? super T, ? extends U> function) {
-+ return this.thenApplyAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public <U> Completable<U> thenApplyAsync(final Function<? super T, ? extends U> function, final Executor executor) {
-+ return this.thenApplyAsync(function, executor, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public <U> Completable<U> thenApplyAsync(final Function<? super T, ? extends U> function, final Executor executor, final Function<? super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(function, "Function may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable<U> ret = new Completable<>();
-+ this.pushStackOrRun(new ApplyTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function));
-+ return ret;
-+ }
-+
-+
-+ public Completable<Void> thenAccept(final Consumer<? super T> consumer) {
-+ return this.thenAccept(consumer, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable<Void> thenAccept(final Consumer<? super T> consumer, final Function<? super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(consumer, "Consumer may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable<Void> ret = new Completable<>();
-+ this.pushStackOrRun(new AcceptTransform<>(null, this, ret, exceptionHandler, consumer));
-+ return ret;
-+ }
-+
-+ public Completable<Void> thenAcceptAsync(final Consumer<? super T> consumer) {
-+ return this.thenAcceptAsync(consumer, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable<Void> thenAcceptAsync(final Consumer<? super T> consumer, final Executor executor) {
-+ return this.thenAcceptAsync(consumer, executor, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable<Void> thenAcceptAsync(final Consumer<? super T> consumer, final Executor executor, final Function<? super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(consumer, "Consumer may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable<Void> ret = new Completable<>();
-+ this.pushStackOrRun(new AcceptTransform<>(checkExecutor(executor), this, ret, exceptionHandler, consumer));
-+ return ret;
-+ }
-+
-+
-+ public Completable<Void> thenRun(final Runnable run) {
-+ return this.thenRun(run, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable<Void> thenRun(final Runnable run, final Function<? super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(run, "Run may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable<Void> ret = new Completable<>();
-+ this.pushStackOrRun(new RunTransform<>(null, this, ret, exceptionHandler, run));
-+ return ret;
-+ }
-+
-+ public Completable<Void> thenRunAsync(final Runnable run) {
-+ return this.thenRunAsync(run, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable<Void> thenRunAsync(final Runnable run, final Executor executor) {
-+ return this.thenRunAsync(run, executor, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable<Void> thenRunAsync(final Runnable run, final Executor executor, final Function<? super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(run, "Run may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable<Void> ret = new Completable<>();
-+ this.pushStackOrRun(new RunTransform<>(checkExecutor(executor), this, ret, exceptionHandler, run));
-+ return ret;
-+ }
-+
-+
-+ public <U> Completable<U> handle(final BiFunction<? super T, ? super Throwable, ? extends U> function) {
-+ return this.handle(function, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public <U> Completable<U> handle(final BiFunction<? super T, ? super Throwable, ? extends U> function,
-+ final Function<? super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(function, "Function may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable<U> ret = new Completable<>();
-+ this.pushStackOrRun(new HandleTransform<>(null, this, ret, exceptionHandler, function));
-+ return ret;
-+ }
-+
-+ public <U> Completable<U> handleAsync(final BiFunction<? super T, ? super Throwable, ? extends U> function) {
-+ return this.handleAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public <U> Completable<U> handleAsync(final BiFunction<? super T, ? super Throwable, ? extends U> function,
-+ final Executor executor) {
-+ return this.handleAsync(function, executor, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public <U> Completable<U> handleAsync(final BiFunction<? super T, ? super Throwable, ? extends U> function,
-+ final Executor executor,
-+ final Function<? super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(function, "Function may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable<U> ret = new Completable<>();
-+ this.pushStackOrRun(new HandleTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function));
-+ return ret;
-+ }
-+
-+
-+ public Completable<T> whenComplete(final BiConsumer<? super T, ? super Throwable> consumer) {
-+ return this.whenComplete(consumer, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable<T> whenComplete(final BiConsumer<? super T, ? super Throwable> consumer, final Function<? super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(consumer, "Consumer may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable<T> ret = new Completable<>();
-+ this.pushStackOrRun(new WhenTransform<>(null, this, ret, exceptionHandler, consumer));
-+ return ret;
-+ }
-+
-+ public Completable<T> whenCompleteAsync(final BiConsumer<? super T, ? super Throwable> consumer) {
-+ return this.whenCompleteAsync(consumer, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable<T> whenCompleteAsync(final BiConsumer<? super T, ? super Throwable> consumer, final Executor executor) {
-+ return this.whenCompleteAsync(consumer, executor, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable<T> whenCompleteAsync(final BiConsumer<? super T, ? super Throwable> consumer, final Executor executor,
-+ final Function<? super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(consumer, "Consumer may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable<T> ret = new Completable<>();
-+ this.pushStackOrRun(new WhenTransform<>(checkExecutor(executor), this, ret, exceptionHandler, consumer));
-+ return ret;
-+ }
-+
-+
-+ public Completable<T> exceptionally(final Function<Throwable, ? extends T> function) {
-+ return this.exceptionally(function, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable<T> exceptionally(final Function<Throwable, ? extends T> function, final Function<? super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(function, "Function may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable<T> ret = new Completable<>();
-+ this.pushStackOrRun(new ExceptionallyTransform<>(null, this, ret, exceptionHandler, function));
-+ return ret;
-+ }
-+
-+ public Completable<T> exceptionallyAsync(final Function<Throwable, ? extends T> function) {
-+ return this.exceptionallyAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable<T> exceptionallyAsync(final Function<Throwable, ? extends T> function, final Executor executor) {
-+ return this.exceptionallyAsync(function, executor, DEFAULT_EXCEPTION_HANDLER);
-+ }
-+
-+ public Completable<T> exceptionallyAsync(final Function<Throwable, ? extends T> function, final Executor executor,
-+ final Function<? super Throwable, ? extends Throwable> exceptionHandler) {
-+ Validate.notNull(function, "Function may not be null");
-+ Validate.notNull(exceptionHandler, "Exception handler may not be null");
-+
-+ final Completable<T> ret = new Completable<>();
-+ this.pushStackOrRun(new ExceptionallyTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function));
-+ return ret;
-+ }
-+
-+ private static final class ExceptionResult {
-+ public final Throwable ex;
-+
-+ public ExceptionResult(final Throwable ex) {
-+ this.ex = ex;
-+ }
-+ }
-+
-+ private static abstract class Transform<U, T> implements Runnable, CompletableFuture.AsynchronousCompletionTask {
-+
-+ private Transform<?, T> next;
-+
-+ private final Executor executor;
-+ protected final Completable<T> from;
-+ protected final Completable<U> to;
-+ protected final Function<? super Throwable, ? extends Throwable> exceptionHandler;
-+
-+ protected Transform(final Executor executor, final Completable<T> from, final Completable<U> to,
-+ final Function<? super Throwable, ? extends Throwable> exceptionHandler) {
-+ this.executor = executor;
-+ this.from = from;
-+ this.to = to;
-+ this.exceptionHandler = exceptionHandler;
-+ }
-+
-+ // force interface call to become virtual call
-+ @Override
-+ public abstract void run();
-+
-+ protected void failed(final Throwable throwable) {
-+ Throwable complete;
-+ try {
-+ complete = this.exceptionHandler.apply(throwable);
-+ } catch (final Throwable thr2) {
-+ throwable.addSuppressed(thr2);
-+ complete = throwable;
-+ }
-+ this.to.completeExceptionally(complete);
-+ }
-+
-+ public void execute() {
-+ if (this.executor == null) {
-+ this.run();
-+ return;
-+ }
-+
-+ try {
-+ this.executor.execute(this);
-+ } catch (final Throwable throwable) {
-+ this.failed(throwable);
-+ }
-+ }
-+ }
-+
-+ private static final class ApplyTransform<U, T> extends Transform<U, T> {
-+
-+ private final Function<? super T, ? extends U> function;
-+
-+ public ApplyTransform(final Executor executor, final Completable<T> from, final Completable<U> to,
-+ final Function<? super Throwable, ? extends Throwable> exceptionHandler,
-+ final Function<? super T, ? extends U> function) {
-+ super(executor, from, to, exceptionHandler);
-+ this.function = function;
-+ }
-+
-+ @Override
-+ public void run() {
-+ final Object result = this.from.getResultPlain();
-+ try {
-+ if (result instanceof ExceptionResult exRes) {
-+ this.to.completeExceptionally(exRes.ex);
-+ } else {
-+ this.to.complete(this.function.apply((T)unmaskNull(result)));
-+ }
-+ } catch (final Throwable throwable) {
-+ this.failed(throwable);
-+ }
-+ }
-+ }
-+
-+ private static final class AcceptTransform<T> extends Transform<Void, T> {
-+ private final Consumer<? super T> consumer;
-+
-+ public AcceptTransform(final Executor executor, final Completable<T> from, final Completable<Void> to,
-+ final Function<? super Throwable, ? extends Throwable> exceptionHandler,
-+ final Consumer<? super T> consumer) {
-+ super(executor, from, to, exceptionHandler);
-+ this.consumer = consumer;
-+ }
-+
-+ @Override
-+ public void run() {
-+ final Object result = this.from.getResultPlain();
-+ try {
-+ if (result instanceof ExceptionResult exRes) {
-+ this.to.completeExceptionally(exRes.ex);
-+ } else {
-+ this.consumer.accept((T)unmaskNull(result));
-+ this.to.complete(null);
-+ }
-+ } catch (final Throwable throwable) {
-+ this.failed(throwable);
-+ }
-+ }
-+ }
-+
-+ private static final class RunTransform<T> extends Transform<Void, T> {
-+ private final Runnable run;
-+
-+ public RunTransform(final Executor executor, final Completable<T> from, final Completable<Void> to,
-+ final Function<? super Throwable, ? extends Throwable> exceptionHandler,
-+ final Runnable run) {
-+ super(executor, from, to, exceptionHandler);
-+ this.run = run;
-+ }
-+
-+ @Override
-+ public void run() {
-+ final Object result = this.from.getResultPlain();
-+ try {
-+ if (result instanceof ExceptionResult exRes) {
-+ this.to.completeExceptionally(exRes.ex);
-+ } else {
-+ this.run.run();
-+ this.to.complete(null);
-+ }
-+ } catch (final Throwable throwable) {
-+ this.failed(throwable);
-+ }
-+ }
-+ }
-+
-+ private static final class HandleTransform<U, T> extends Transform<U, T> {
-+
-+ private final BiFunction<? super T, ? super Throwable, ? extends U> function;
-+
-+ public HandleTransform(final Executor executor, final Completable<T> from, final Completable<U> to,
-+ final Function<? super Throwable, ? extends Throwable> exceptionHandler,
-+ final BiFunction<? super T, ? super Throwable, ? extends U> function) {
-+ super(executor, from, to, exceptionHandler);
-+ this.function = function;
-+ }
-+
-+ @Override
-+ public void run() {
-+ final Object result = this.from.getResultPlain();
-+ try {
-+ if (result instanceof ExceptionResult exRes) {
-+ this.to.complete(this.function.apply(null, exRes.ex));
-+ } else {
-+ this.to.complete(this.function.apply((T)unmaskNull(result), null));
-+ }
-+ } catch (final Throwable throwable) {
-+ this.failed(throwable);
-+ }
-+ }
-+ }
-+
-+ private static final class WhenTransform<T> extends Transform<T, T> {
-+
-+ private final BiConsumer<? super T, ? super Throwable> consumer;
-+
-+ public WhenTransform(final Executor executor, final Completable<T> from, final Completable<T> to,
-+ final Function<? super Throwable, ? extends Throwable> exceptionHandler,
-+ final BiConsumer<? super T, ? super Throwable> consumer) {
-+ super(executor, from, to, exceptionHandler);
-+ this.consumer = consumer;
-+ }
-+
-+ @Override
-+ public void run() {
-+ final Object result = this.from.getResultPlain();
-+ try {
-+ if (result instanceof ExceptionResult exRes) {
-+ this.consumer.accept(null, exRes.ex);
-+ this.to.completeExceptionally(exRes.ex);
-+ } else {
-+ final T unmasked = (T)unmaskNull(result);
-+ this.consumer.accept(unmasked, null);
-+ this.to.complete(unmasked);
-+ }
-+ } catch (final Throwable throwable) {
-+ this.failed(throwable);
-+ }
-+ }
-+ }
-+
-+ private static final class ExceptionallyTransform<T> extends Transform<T, T> {
-+ private final Function<Throwable, ? extends T> function;
-+
-+ public ExceptionallyTransform(final Executor executor, final Completable<T> from, final Completable<T> to,
-+ final Function<? super Throwable, ? extends Throwable> exceptionHandler,
-+ final Function<Throwable, ? extends T> function) {
-+ super(executor, from, to, exceptionHandler);
-+ this.function = function;
-+ }
-+
-+ @Override
-+ public void run() {
-+ final Object result = this.from.getResultPlain();
-+ try {
-+ if (result instanceof ExceptionResult exRes) {
-+ this.to.complete(this.function.apply(exRes.ex));
-+ } else {
-+ this.to.complete((T)unmaskNull(result));
-+ }
-+ } catch (final Throwable throwable) {
-+ this.failed(throwable);
-+ }
-+ }
-+ }
-+
-+ private static final class UnparkTransform<T> extends Transform<Void, T> implements ForkJoinPool.ManagedBlocker {
-+
-+ private volatile Thread thread;
-+
-+ public UnparkTransform(final Completable<T> from, final Thread target) {
-+ super(null, from, null, null);
-+ this.thread = target;
-+ }
-+
-+ @Override
-+ public void run() {
-+ final Thread t = this.thread;
-+ this.thread = null;
-+ LockSupport.unpark(t);
-+ }
-+
-+ @Override
-+ public boolean block() throws InterruptedException {
-+ while (!this.isReleasable()) {
-+ if (Thread.interrupted()) {
-+ throw new InterruptedException();
-+ }
-+ LockSupport.park(this);
-+ }
-+
-+ return true;
-+ }
-+
-+ @Override
-+ public boolean isReleasable() {
-+ return this.thread == null;
-+ }
-+ }
-+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/Cancellable.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/Cancellable.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..11449056361bb6c5a055f543cdd135c4113757c6
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/Cancellable.java
-@@ -0,0 +1,14 @@
-+package ca.spottedleaf.concurrentutil.executor;
-+
-+/**
-+ * Interface specifying that something can be cancelled.
-+ */
-+public interface Cancellable {
-+
-+ /**
-+ * Tries to cancel this task. If the task is in a stage that is too late to be cancelled, then this function
-+ * will return {@code false}. If the task is already cancelled, then this function returns {@code false}. Only
-+ * when this function successfully stops this task from being completed will it return {@code true}.
-+ */
-+ public boolean cancel();
-+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..17cbaee1e89bd3f6d905e640d20d0119ab0570a0
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/PrioritisedExecutor.java
-@@ -0,0 +1,271 @@
-+package ca.spottedleaf.concurrentutil.executor;
-+
-+import ca.spottedleaf.concurrentutil.util.Priority;
-+
-+public interface PrioritisedExecutor {
-+
-+ /**
-+ * Returns the number of tasks that have been scheduled are pending to be scheduled.
-+ */
-+ public long getTotalTasksScheduled();
-+
-+ /**
-+ * Returns the number of tasks that have been executed.
-+ */
-+ public long getTotalTasksExecuted();
-+
-+ /**
-+ * Generates the next suborder id.
-+ * @return The next suborder id.
-+ */
-+ public long generateNextSubOrder();
-+
-+ /**
-+ * Executes the next available task.
-+ * <p>
-+ * If there is a task with priority {@link Priority#BLOCKING} available, then that such task is executed.
-+ * </p>
-+ * <p>
-+ * If there is a task with priority {@link Priority#IDLE} available then that task is only executed
-+ * when there are no other tasks available with a higher priority.
-+ * </p>
-+ * <p>
-+ * If there are no tasks that have priority {@link Priority#BLOCKING} or {@link Priority#IDLE}, then
-+ * this function will be biased to execute tasks that have higher priorities.
-+ * </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
-+ */
-+ public boolean executeTask() throws IllegalStateException;
-+
-+ /**
-+ * Prevent further additions to this executor. Attempts to add after this call has completed (potentially during) will
-+ * result in {@link IllegalStateException} being thrown.
-+ * <p>
-+ * This operation is atomic with respect to other shutdown calls
-+ * </p>
-+ * <p>
-+ * After this call has completed, regardless of return value, this executor will be shutdown.
-+ * </p>
-+ *
-+ * @return {@code true} if the executor was shutdown, {@code false} if it has shut down already
-+ * @see #isShutdown()
-+ */
-+ public boolean shutdown();
-+
-+ /**
-+ * Returns whether this executor has shut down. Effectively, returns whether new tasks will be rejected.
-+ * This method does not indicate whether all the tasks scheduled have been executed.
-+ * @return Returns whether this executor has shut down.
-+ */
-+ public boolean isShutdown();
-+
-+ /**
-+ * Queues or executes a task at {@link Priority#NORMAL} priority.
-+ * @param task The task to run.
-+ *
-+ * @throws IllegalStateException If this executor has shutdown.
-+ * @throws NullPointerException If the task is null
-+ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task
-+ * associated with the parameter
-+ */
-+ public PrioritisedTask queueTask(final Runnable task);
-+
-+ /**
-+ * Queues or executes a task.
-+ *
-+ * @param task The task to run.
-+ * @param priority The priority for the task.
-+ *
-+ * @throws IllegalStateException If this executor has shutdown.
-+ * @throws NullPointerException If the task is null
-+ * @throws IllegalArgumentException If the priority is invalid.
-+ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task
-+ * associated with the parameter
-+ */
-+ public PrioritisedTask queueTask(final Runnable task, final Priority priority);
-+
-+ /**
-+ * Queues or executes a task.
-+ *
-+ * @param task The task to run.
-+ * @param priority The priority for the task.
-+ * @param subOrder The task's suborder.
-+ *
-+ * @throws IllegalStateException If this executor has shutdown.
-+ * @throws NullPointerException If the task is null
-+ * @throws IllegalArgumentException If the priority is invalid.
-+ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task
-+ * associated with the parameter
-+ */
-+ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder);
-+
-+ /**
-+ * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority.
-+ * @param task The task to run.
-+ *
-+ * @throws NullPointerException If the task is null
-+ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task
-+ * associated with the parameter
-+ */
-+ public PrioritisedTask createTask(final Runnable task);
-+
-+ /**
-+ * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority.
-+ *
-+ * @param task The task to run.
-+ * @param priority The priority for the task.
-+ *
-+ * @throws NullPointerException If the task is null
-+ * @throws IllegalArgumentException If the priority is invalid.
-+ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task
-+ * associated with the parameter
-+ */
-+ public PrioritisedTask createTask(final Runnable task, final Priority priority);
-+
-+ /**
-+ * Creates, but does not queue or execute, a task at {@link Priority#NORMAL} priority.
-+ *
-+ * @param task The task to run.
-+ * @param priority The priority for the task.
-+ * @param subOrder The task's suborder.
-+ *
-+ * @throws NullPointerException If the task is null
-+ * @throws IllegalArgumentException If the priority is invalid.
-+ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task
-+ * associated with the parameter
-+ */
-+ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder);
-+
-+ public static interface PrioritisedTask extends Cancellable {
-+
-+ /**
-+ * Returns the executor associated with this task.
-+ * @return The executor associated with this task.
-+ */
-+ public PrioritisedExecutor getExecutor();
-+
-+ /**
-+ * Causes a lazily queued task to become queued or executed
-+ *
-+ * @throws IllegalStateException If the backing executor has shutdown
-+ * @return {@code true} If the task was queued, {@code false} if the task was already queued/cancelled/executed
-+ */
-+ public boolean queue();
-+
-+ /**
-+ * Returns whether this task has been queued and is not completing.
-+ * @return {@code true} If the task has been queued, {@code false} if the task has not been queued or is marked
-+ * as completing.
-+ */
-+ public boolean isQueued();
-+
-+ /**
-+ * Forces this task to be marked as completed.
-+ *
-+ * @return {@code true} if the task was cancelled, {@code false} if the task has already completed
-+ * or is being completed.
-+ */
-+ @Override
-+ public boolean cancel();
-+
-+ /**
-+ * Executes this task. This will also mark the task as completing.
-+ * <p>
-+ * Exceptions thrown from the runnable will be rethrown.
-+ * </p>
-+ *
-+ * @return {@code true} if this task was executed, {@code false} if it was already marked as completed.
-+ */
-+ public boolean execute();
-+
-+ /**
-+ * Returns the current priority. Note that {@link Priority#COMPLETING} will be returned
-+ * if this task is completing or has completed.
-+ */
-+ public Priority getPriority();
-+
-+ /**
-+ * Attempts to set this task's priority level to the level specified.
-+ *
-+ * @param priority Specified priority level.
-+ *
-+ * @throws IllegalArgumentException If the priority is invalid
-+ * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue
-+ * this task was scheduled on was shutdown, or if the priority was already at the specified level.
-+ */
-+ public boolean setPriority(final Priority priority);
-+
-+ /**
-+ * Attempts to raise the priority to the priority level specified.
-+ *
-+ * @param priority Priority specified
-+ *
-+ * @throws IllegalArgumentException If the priority is invalid
-+ * @return {@code false} if the current task is completing, {@code true} if the priority was raised to the
-+ * specified level or was already at the specified level or higher.
-+ */
-+ public boolean raisePriority(final Priority priority);
-+
-+ /**
-+ * Attempts to lower the priority to the priority level specified.
-+ *
-+ * @param priority Priority specified
-+ *
-+ * @throws IllegalArgumentException If the priority is invalid
-+ * @return {@code false} if the current task is completing, {@code true} if the priority was lowered to the
-+ * specified level or was already at the specified level or lower.
-+ */
-+ public boolean lowerPriority(final Priority priority);
-+
-+ /**
-+ * Returns the suborder id associated with this task.
-+ * @return The suborder id associated with this task.
-+ */
-+ public long getSubOrder();
-+
-+ /**
-+ * Sets the suborder id associated with this task. Ths function has no effect when this task
-+ * is completing or is completed.
-+ *
-+ * @param subOrder Specified new sub order.
-+ *
-+ * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue
-+ * this task was scheduled on was shutdown, or if the current suborder is the same as the new sub order.
-+ */
-+ public boolean setSubOrder(final long subOrder);
-+
-+ /**
-+ * Attempts to raise the suborder to the suborder specified.
-+ *
-+ * @param subOrder Specified new sub order.
-+ *
-+ * @return {@code false} if the current task is completing, {@code true} if the suborder was raised to the
-+ * specified suborder or was already at the specified suborder or higher.
-+ */
-+ public boolean raiseSubOrder(final long subOrder);
-+
-+ /**
-+ * Attempts to lower the suborder to the suborder specified.
-+ *
-+ * @param subOrder Specified new sub order.
-+ *
-+ * @return {@code false} if the current task is completing, {@code true} if the suborder was lowered to the
-+ * specified suborder or was already at the specified suborder or lower.
-+ */
-+ public boolean lowerSubOrder(final long subOrder);
-+
-+ /**
-+ * Sets the priority and suborder id associated with this task. Ths function has no effect when this task
-+ * is completing or is completed.
-+ *
-+ * @param priority Priority specified
-+ * @param subOrder Specified new sub order.
-+ * @return {@code true} if successful, {@code false} if this task is completing or has completed or the queue
-+ * this task was scheduled on was shutdown, or if the current priority and suborder are the same as
-+ * the parameters.
-+ */
-+ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder);
-+ }
-+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..edb8c6611bdc9aced2714b963e00bbb7829603d2
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java
-@@ -0,0 +1,454 @@
-+package ca.spottedleaf.concurrentutil.executor.queue;
-+
-+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
-+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
-+import ca.spottedleaf.concurrentutil.util.Priority;
-+import java.lang.invoke.VarHandle;
-+import java.util.Comparator;
-+import java.util.Map;
-+import java.util.concurrent.ConcurrentSkipListMap;
-+import java.util.concurrent.atomic.AtomicBoolean;
-+import java.util.concurrent.atomic.AtomicLong;
-+
-+public final class PrioritisedTaskQueue implements PrioritisedExecutor {
-+
-+ /**
-+ * Required for tie-breaking in the queue
-+ */
-+ private final AtomicLong taskIdGenerator = new AtomicLong();
-+ private final AtomicLong scheduledTasks = new AtomicLong();
-+ private final AtomicLong executedTasks = new AtomicLong();
-+ private final AtomicLong subOrderGenerator = new AtomicLong();
-+ private final AtomicBoolean shutdown = new AtomicBoolean();
-+ private final ConcurrentSkipListMap<PrioritisedQueuedTask.Holder, Boolean> tasks = new ConcurrentSkipListMap<>(PrioritisedQueuedTask.COMPARATOR);
-+
-+ @Override
-+ public long getTotalTasksScheduled() {
-+ return this.scheduledTasks.get();
-+ }
-+
-+ @Override
-+ public long getTotalTasksExecuted() {
-+ return this.executedTasks.get();
-+ }
-+
-+ @Override
-+ public long generateNextSubOrder() {
-+ return this.subOrderGenerator.getAndIncrement();
-+ }
-+
-+ @Override
-+ public boolean shutdown() {
-+ return !this.shutdown.getAndSet(true);
-+ }
-+
-+ @Override
-+ public boolean isShutdown() {
-+ return this.shutdown.get();
-+ }
-+
-+ public PrioritisedTask peekFirst() {
-+ final Map.Entry<PrioritisedQueuedTask.Holder, Boolean> firstEntry = this.tasks.firstEntry();
-+ return firstEntry == null ? null : firstEntry.getKey().task;
-+ }
-+
-+ public Priority getHighestPriority() {
-+ final Map.Entry<PrioritisedQueuedTask.Holder, Boolean> firstEntry = this.tasks.firstEntry();
-+ return firstEntry == null ? null : Priority.getPriority(firstEntry.getKey().priority);
-+ }
-+
-+ public boolean hasNoScheduledTasks() {
-+ final long executedTasks = this.executedTasks.get();
-+ final long scheduledTasks = this.scheduledTasks.get();
-+
-+ return executedTasks == scheduledTasks;
-+ }
-+
-+ public PrioritySubOrderPair getHighestPrioritySubOrder() {
-+ final Map.Entry<PrioritisedQueuedTask.Holder, Boolean> firstEntry = this.tasks.firstEntry();
-+ if (firstEntry == null) {
-+ return null;
-+ }
-+
-+ final PrioritisedQueuedTask.Holder holder = firstEntry.getKey();
-+
-+ return new PrioritySubOrderPair(Priority.getPriority(holder.priority), holder.subOrder);
-+ }
-+
-+ public Runnable pollTask() {
-+ for (;;) {
-+ final Map.Entry<PrioritisedQueuedTask.Holder, Boolean> firstEntry = this.tasks.pollFirstEntry();
-+ if (firstEntry != null) {
-+ final PrioritisedQueuedTask.Holder task = firstEntry.getKey();
-+ task.markRemoved();
-+ if (!task.task.cancel()) {
-+ continue;
-+ }
-+ return task.task.execute;
-+ }
-+
-+ return null;
-+ }
-+ }
-+
-+ @Override
-+ public boolean executeTask() {
-+ for (;;) {
-+ final Map.Entry<PrioritisedQueuedTask.Holder, Boolean> firstEntry = this.tasks.pollFirstEntry();
-+ if (firstEntry != null) {
-+ final PrioritisedQueuedTask.Holder task = firstEntry.getKey();
-+ task.markRemoved();
-+ if (!task.task.execute()) {
-+ continue;
-+ }
-+ return true;
-+ }
-+
-+ return false;
-+ }
-+ }
-+
-+ @Override
-+ public PrioritisedTask createTask(final Runnable task) {
-+ return this.createTask(task, Priority.NORMAL, this.generateNextSubOrder());
-+ }
-+
-+ @Override
-+ public PrioritisedTask createTask(final Runnable task, final Priority priority) {
-+ return this.createTask(task, priority, this.generateNextSubOrder());
-+ }
-+
-+ @Override
-+ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) {
-+ return new PrioritisedQueuedTask(task, priority, subOrder);
-+ }
-+
-+ @Override
-+ public PrioritisedTask queueTask(final Runnable task) {
-+ return this.queueTask(task, Priority.NORMAL, this.generateNextSubOrder());
-+ }
-+
-+ @Override
-+ public PrioritisedTask queueTask(final Runnable task, final Priority priority) {
-+ return this.queueTask(task, priority, this.generateNextSubOrder());
-+ }
-+
-+ @Override
-+ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) {
-+ final PrioritisedQueuedTask ret = new PrioritisedQueuedTask(task, priority, subOrder);
-+
-+ ret.queue();
-+
-+ return ret;
-+ }
-+
-+ private final class PrioritisedQueuedTask implements PrioritisedExecutor.PrioritisedTask {
-+ public static final Comparator<PrioritisedQueuedTask.Holder> COMPARATOR = (final PrioritisedQueuedTask.Holder t1, final PrioritisedQueuedTask.Holder t2) -> {
-+ final int priorityCompare = t1.priority - t2.priority;
-+ if (priorityCompare != 0) {
-+ return priorityCompare;
-+ }
-+
-+ final int subOrderCompare = Long.compare(t1.subOrder, t2.subOrder);
-+ if (subOrderCompare != 0) {
-+ return subOrderCompare;
-+ }
-+
-+ return Long.compare(t1.id, t2.id);
-+ };
-+
-+ private static final class Holder {
-+ private final PrioritisedQueuedTask task;
-+ private final int priority;
-+ private final long subOrder;
-+ private final long id;
-+
-+ private volatile boolean removed;
-+ private static final VarHandle REMOVED_HANDLE = ConcurrentUtil.getVarHandle(Holder.class, "removed", boolean.class);
-+
-+ private Holder(final PrioritisedQueuedTask task, final int priority, final long subOrder,
-+ final long id) {
-+ this.task = task;
-+ this.priority = priority;
-+ this.subOrder = subOrder;
-+ this.id = id;
-+ }
-+
-+ /**
-+ * Returns true if marked as removed
-+ */
-+ public boolean markRemoved() {
-+ return !(boolean)REMOVED_HANDLE.getAndSet((Holder)this, (boolean)true);
-+ }
-+ }
-+
-+ private final long id;
-+ private final Runnable execute;
-+
-+ private Priority priority;
-+ private long subOrder;
-+ private Holder holder;
-+
-+ public PrioritisedQueuedTask(final Runnable execute, final Priority priority, final long subOrder) {
-+ if (!Priority.isValidPriority(priority)) {
-+ throw new IllegalArgumentException("Invalid priority " + priority);
-+ }
-+
-+ this.execute = execute;
-+ this.priority = priority;
-+ this.subOrder = subOrder;
-+ this.id = PrioritisedTaskQueue.this.taskIdGenerator.getAndIncrement();
-+ }
-+
-+ @Override
-+ public PrioritisedExecutor getExecutor() {
-+ return PrioritisedTaskQueue.this;
-+ }
-+
-+ @Override
-+ public boolean queue() {
-+ synchronized (this) {
-+ if (this.holder != null || this.priority == Priority.COMPLETING) {
-+ return false;
-+ }
-+
-+ if (PrioritisedTaskQueue.this.isShutdown()) {
-+ throw new IllegalStateException("Queue is shutdown");
-+ }
-+
-+ final Holder holder = new Holder(this, this.priority.priority, this.subOrder, this.id);
-+ this.holder = holder;
-+
-+ PrioritisedTaskQueue.this.scheduledTasks.getAndIncrement();
-+ PrioritisedTaskQueue.this.tasks.put(holder, Boolean.TRUE);
-+ }
-+
-+ if (PrioritisedTaskQueue.this.isShutdown()) {
-+ this.cancel();
-+ throw new IllegalStateException("Queue is shutdown");
-+ }
-+
-+
-+ return true;
-+ }
-+
-+ @Override
-+ public boolean isQueued() {
-+ synchronized (this) {
-+ return this.holder != null && this.priority != Priority.COMPLETING;
-+ }
-+ }
-+
-+ @Override
-+ public boolean cancel() {
-+ synchronized (this) {
-+ if (this.priority == Priority.COMPLETING) {
-+ return false;
-+ }
-+
-+ this.priority = Priority.COMPLETING;
-+
-+ if (this.holder != null) {
-+ if (this.holder.markRemoved()) {
-+ PrioritisedTaskQueue.this.tasks.remove(this.holder);
-+ }
-+ PrioritisedTaskQueue.this.executedTasks.getAndIncrement();
-+ }
-+
-+ return true;
-+ }
-+ }
-+
-+ @Override
-+ public boolean execute() {
-+ final boolean increaseExecuted;
-+
-+ synchronized (this) {
-+ if (this.priority == Priority.COMPLETING) {
-+ return false;
-+ }
-+
-+ this.priority = Priority.COMPLETING;
-+
-+ if (increaseExecuted = (this.holder != null)) {
-+ if (this.holder.markRemoved()) {
-+ PrioritisedTaskQueue.this.tasks.remove(this.holder);
-+ }
-+ }
-+ }
-+
-+ try {
-+ this.execute.run();
-+ return true;
-+ } finally {
-+ if (increaseExecuted) {
-+ PrioritisedTaskQueue.this.executedTasks.getAndIncrement();
-+ }
-+ }
-+ }
-+
-+ @Override
-+ public Priority getPriority() {
-+ synchronized (this) {
-+ return this.priority;
-+ }
-+ }
-+
-+ @Override
-+ public boolean setPriority(final Priority priority) {
-+ synchronized (this) {
-+ if (this.priority == Priority.COMPLETING || this.priority == priority) {
-+ return false;
-+ }
-+
-+ this.priority = priority;
-+
-+ if (this.holder != null) {
-+ if (this.holder.markRemoved()) {
-+ PrioritisedTaskQueue.this.tasks.remove(this.holder);
-+ }
-+ this.holder = new Holder(this, priority.priority, this.subOrder, this.id);
-+ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
-+ }
-+
-+ return true;
-+ }
-+ }
-+
-+ @Override
-+ public boolean raisePriority(final Priority priority) {
-+ synchronized (this) {
-+ if (this.priority == Priority.COMPLETING || this.priority.isHigherOrEqualPriority(priority)) {
-+ return false;
-+ }
-+
-+ this.priority = priority;
-+
-+ if (this.holder != null) {
-+ if (this.holder.markRemoved()) {
-+ PrioritisedTaskQueue.this.tasks.remove(this.holder);
-+ }
-+ this.holder = new Holder(this, priority.priority, this.subOrder, this.id);
-+ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
-+ }
-+
-+ return true;
-+ }
-+ }
-+
-+ @Override
-+ public boolean lowerPriority(Priority priority) {
-+ synchronized (this) {
-+ if (this.priority == Priority.COMPLETING || this.priority.isLowerOrEqualPriority(priority)) {
-+ return false;
-+ }
-+
-+ this.priority = priority;
-+
-+ if (this.holder != null) {
-+ if (this.holder.markRemoved()) {
-+ PrioritisedTaskQueue.this.tasks.remove(this.holder);
-+ }
-+ this.holder = new Holder(this, priority.priority, this.subOrder, this.id);
-+ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
-+ }
-+
-+ return true;
-+ }
-+ }
-+
-+ @Override
-+ public long getSubOrder() {
-+ synchronized (this) {
-+ return this.subOrder;
-+ }
-+ }
-+
-+ @Override
-+ public boolean setSubOrder(final long subOrder) {
-+ synchronized (this) {
-+ if (this.priority == Priority.COMPLETING || this.subOrder == subOrder) {
-+ return false;
-+ }
-+
-+ this.subOrder = subOrder;
-+
-+ if (this.holder != null) {
-+ if (this.holder.markRemoved()) {
-+ PrioritisedTaskQueue.this.tasks.remove(this.holder);
-+ }
-+ this.holder = new Holder(this, priority.priority, this.subOrder, this.id);
-+ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
-+ }
-+
-+ return true;
-+ }
-+ }
-+
-+ @Override
-+ public boolean raiseSubOrder(long subOrder) {
-+ synchronized (this) {
-+ if (this.priority == Priority.COMPLETING || this.subOrder >= subOrder) {
-+ return false;
-+ }
-+
-+ this.subOrder = subOrder;
-+
-+ if (this.holder != null) {
-+ if (this.holder.markRemoved()) {
-+ PrioritisedTaskQueue.this.tasks.remove(this.holder);
-+ }
-+ this.holder = new Holder(this, priority.priority, this.subOrder, this.id);
-+ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
-+ }
-+
-+ return true;
-+ }
-+ }
-+
-+ @Override
-+ public boolean lowerSubOrder(final long subOrder) {
-+ synchronized (this) {
-+ if (this.priority == Priority.COMPLETING || this.subOrder <= subOrder) {
-+ return false;
-+ }
-+
-+ this.subOrder = subOrder;
-+
-+ if (this.holder != null) {
-+ if (this.holder.markRemoved()) {
-+ PrioritisedTaskQueue.this.tasks.remove(this.holder);
-+ }
-+ this.holder = new Holder(this, priority.priority, this.subOrder, this.id);
-+ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
-+ }
-+
-+ return true;
-+ }
-+ }
-+
-+ @Override
-+ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) {
-+ synchronized (this) {
-+ if (this.priority == Priority.COMPLETING || (this.priority == priority && this.subOrder == subOrder)) {
-+ return false;
-+ }
-+
-+ this.priority = priority;
-+ this.subOrder = subOrder;
-+
-+ if (this.holder != null) {
-+ if (this.holder.markRemoved()) {
-+ PrioritisedTaskQueue.this.tasks.remove(this.holder);
-+ }
-+ this.holder = new Holder(this, priority.priority, this.subOrder, this.id);
-+ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
-+ }
-+
-+ return true;
-+ }
-+ }
-+ }
-+
-+ public static record PrioritySubOrderPair(Priority priority, long subOrder) {}
-+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..f5367a13aaa02f0f929813c00a67e6ac7c8652cb
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedQueueExecutorThread.java
-@@ -0,0 +1,402 @@
-+package ca.spottedleaf.concurrentutil.executor.thread;
-+
-+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
-+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
-+import ca.spottedleaf.concurrentutil.util.Priority;
-+import org.slf4j.Logger;
-+import org.slf4j.LoggerFactory;
-+import java.lang.invoke.VarHandle;
-+import java.util.concurrent.locks.LockSupport;
-+
-+/**
-+ * Thread which will continuously drain from a specified queue.
-+ * <p>
-+ * Note: When using this thread, queue additions to the underlying {@link #queue} are not sufficient to get this thread
-+ * to execute the task. The function {@link #notifyTasks()} must be used after scheduling a task. For expected behaviour
-+ * of task scheduling, use the methods provided on this class to schedule tasks.
-+ * </p>
-+ */
-+public class PrioritisedQueueExecutorThread extends Thread implements PrioritisedExecutor {
-+
-+ private static final Logger LOGGER = LoggerFactory.getLogger(PrioritisedQueueExecutorThread.class);
-+
-+ protected final PrioritisedExecutor queue;
-+
-+ protected volatile boolean threadShutdown;
-+
-+ protected volatile boolean threadParked;
-+ protected static final VarHandle THREAD_PARKED_HANDLE = ConcurrentUtil.getVarHandle(PrioritisedQueueExecutorThread.class, "threadParked", boolean.class);
-+
-+ protected volatile boolean halted;
-+
-+ protected final long spinWaitTime;
-+
-+ protected static final long DEFAULT_SPINWAIT_TIME = (long)(0.1e6);// 0.1ms
-+
-+ public PrioritisedQueueExecutorThread(final PrioritisedExecutor queue) {
-+ this(queue, DEFAULT_SPINWAIT_TIME); // 0.1ms
-+ }
-+
-+ public PrioritisedQueueExecutorThread(final PrioritisedExecutor queue, final long spinWaitTime) { // in ns
-+ this.queue = queue;
-+ this.spinWaitTime = spinWaitTime;
-+ }
-+
-+ @Override
-+ public final void run() {
-+ try {
-+ this.begin();
-+ this.doRun();
-+ } finally {
-+ this.die();
-+ }
-+ }
-+
-+ public final void doRun() {
-+ final long spinWaitTime = this.spinWaitTime;
-+
-+ main_loop:
-+ for (;;) {
-+ this.pollTasks();
-+
-+ // spinwait
-+
-+ final long start = System.nanoTime();
-+
-+ for (;;) {
-+ // If we are interrupted for any reason, park() will always return immediately. Clear so that we don't needlessly use cpu in such an event.
-+ Thread.interrupted();
-+ Thread.yield();
-+ LockSupport.parkNanos("Spinwaiting on tasks", 10_000L); // 10us
-+
-+ if (this.pollTasks()) {
-+ // restart loop, found tasks
-+ continue main_loop;
-+ }
-+
-+ if (this.handleClose()) {
-+ return; // we're done
-+ }
-+
-+ if ((System.nanoTime() - start) >= spinWaitTime) {
-+ break;
-+ }
-+ }
-+
-+ if (this.handleClose()) {
-+ return;
-+ }
-+
-+ this.setThreadParkedVolatile(true);
-+
-+ // We need to parse here to avoid a race condition where a thread queues a task before we set parked to true
-+ // (i.e. it will not notify us)
-+ if (this.pollTasks()) {
-+ this.setThreadParkedVolatile(false);
-+ continue;
-+ }
-+
-+ if (this.handleClose()) {
-+ return;
-+ }
-+
-+ // we don't need to check parked before sleeping, but we do need to check parked in a do-while loop
-+ // LockSupport.park() can fail for any reason
-+ while (this.getThreadParkedVolatile()) {
-+ Thread.interrupted();
-+ LockSupport.park("Waiting on tasks");
-+ }
-+ }
-+ }
-+
-+ protected void begin() {}
-+
-+ protected void die() {}
-+
-+ /**
-+ * Attempts to poll as many tasks as possible, returning when finished.
-+ * @return Whether any tasks were executed.
-+ */
-+ protected boolean pollTasks() {
-+ boolean ret = false;
-+
-+ for (;;) {
-+ if (this.halted) {
-+ break;
-+ }
-+ try {
-+ if (!this.queue.executeTask()) {
-+ break;
-+ }
-+ ret = true;
-+ } catch (final Throwable throwable) {
-+ LOGGER.error("Exception thrown from prioritized runnable task in thread '" + this.getName() + "'", throwable);
-+ }
-+ }
-+
-+ return ret;
-+ }
-+
-+ protected boolean handleClose() {
-+ if (this.threadShutdown) {
-+ this.pollTasks(); // this ensures we've emptied the queue
-+ return true;
-+ }
-+ return false;
-+ }
-+
-+ /**
-+ * Notify this thread that a task has been added to its queue
-+ * @return {@code true} if this thread was waiting for tasks, {@code false} if it is executing tasks
-+ */
-+ public boolean notifyTasks() {
-+ if (this.getThreadParkedVolatile() && this.exchangeThreadParkedVolatile(false)) {
-+ LockSupport.unpark(this);
-+ return true;
-+ }
-+ return false;
-+ }
-+
-+ @Override
-+ public long getTotalTasksExecuted() {
-+ return this.queue.getTotalTasksExecuted();
-+ }
-+
-+ @Override
-+ public long getTotalTasksScheduled() {
-+ return this.queue.getTotalTasksScheduled();
-+ }
-+
-+ @Override
-+ public long generateNextSubOrder() {
-+ return this.queue.generateNextSubOrder();
-+ }
-+
-+ @Override
-+ public boolean shutdown() {
-+ throw new UnsupportedOperationException();
-+ }
-+
-+ @Override
-+ public boolean isShutdown() {
-+ return false;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ * @throws IllegalStateException Always
-+ */
-+ @Override
-+ public boolean executeTask() throws IllegalStateException {
-+ throw new IllegalStateException();
-+ }
-+
-+ @Override
-+ public PrioritisedTask queueTask(final Runnable task) {
-+ final PrioritisedTask ret = this.createTask(task);
-+
-+ ret.queue();
-+
-+ return ret;
-+ }
-+
-+ @Override
-+ public PrioritisedTask queueTask(final Runnable task, final Priority priority) {
-+ final PrioritisedTask ret = this.createTask(task, priority);
-+
-+ ret.queue();
-+
-+ return ret;
-+ }
-+
-+ @Override
-+ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) {
-+ final PrioritisedTask ret = this.createTask(task, priority, subOrder);
-+
-+ ret.queue();
-+
-+ return ret;
-+ }
-+
-+
-+ @Override
-+ public PrioritisedTask createTask(Runnable task) {
-+ final PrioritisedTask queueTask = this.queue.createTask(task);
-+
-+ return new WrappedTask(queueTask);
-+ }
-+
-+ @Override
-+ public PrioritisedTask createTask(final Runnable task, final Priority priority) {
-+ final PrioritisedTask queueTask = this.queue.createTask(task, priority);
-+
-+ return new WrappedTask(queueTask);
-+ }
-+
-+ @Override
-+ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) {
-+ final PrioritisedTask queueTask = this.queue.createTask(task, priority, subOrder);
-+
-+ return new WrappedTask(queueTask);
-+ }
-+
-+ /**
-+ * Closes this queue executor's queue. Optionally waits for all tasks in queue to be executed if {@code wait} is true.
-+ * <p>
-+ * This function is MT-Safe.
-+ * </p>
-+ * @param wait If this call is to wait until this thread shuts down.
-+ * @param killQueue Whether to shutdown this thread's queue
-+ * @return whether this thread shut down the queue
-+ * @see #halt(boolean)
-+ */
-+ public boolean close(final boolean wait, final boolean killQueue) {
-+ final boolean ret = killQueue && this.queue.shutdown();
-+ this.threadShutdown = true;
-+
-+ // force thread to respond to the shutdown
-+ this.setThreadParkedVolatile(false);
-+ LockSupport.unpark(this);
-+
-+ if (wait) {
-+ boolean interrupted = false;
-+ for (;;) {
-+ if (this.isAlive()) {
-+ if (interrupted) {
-+ Thread.currentThread().interrupt();
-+ }
-+ break;
-+ }
-+ try {
-+ this.join();
-+ } catch (final InterruptedException ex) {
-+ interrupted = true;
-+ }
-+ }
-+ }
-+
-+ return ret;
-+ }
-+
-+
-+ /**
-+ * Causes this thread to exit without draining the queue. To ensure tasks are completed, use {@link #close(boolean, boolean)}.
-+ * <p>
-+ * This is not safe to call with {@link #close(boolean, boolean)} if <code>wait = true</code>, in which case
-+ * the waiting thread may block indefinitely.
-+ * </p>
-+ * <p>
-+ * This function is MT-Safe.
-+ * </p>
-+ * @param killQueue Whether to shutdown this thread's queue
-+ * @see #close(boolean, boolean)
-+ */
-+ public void halt(final boolean killQueue) {
-+ if (killQueue) {
-+ this.queue.shutdown();
-+ }
-+ this.threadShutdown = true;
-+ this.halted = true;
-+
-+ // force thread to respond to the shutdown
-+ this.setThreadParkedVolatile(false);
-+ LockSupport.unpark(this);
-+ }
-+
-+ protected final boolean getThreadParkedVolatile() {
-+ return (boolean)THREAD_PARKED_HANDLE.getVolatile(this);
-+ }
-+
-+ protected final boolean exchangeThreadParkedVolatile(final boolean value) {
-+ return (boolean)THREAD_PARKED_HANDLE.getAndSet(this, value);
-+ }
-+
-+ protected final void setThreadParkedVolatile(final boolean value) {
-+ THREAD_PARKED_HANDLE.setVolatile(this, value);
-+ }
-+
-+ /**
-+ * Required so that queue() can notify (unpark) this thread
-+ */
-+ private final class WrappedTask implements PrioritisedTask {
-+ private final PrioritisedTask queueTask;
-+
-+ public WrappedTask(final PrioritisedTask queueTask) {
-+ this.queueTask = queueTask;
-+ }
-+
-+ @Override
-+ public PrioritisedExecutor getExecutor() {
-+ return PrioritisedQueueExecutorThread.this;
-+ }
-+
-+ @Override
-+ public boolean queue() {
-+ final boolean ret = this.queueTask.queue();
-+ if (ret) {
-+ PrioritisedQueueExecutorThread.this.notifyTasks();
-+ }
-+ return ret;
-+ }
-+
-+ @Override
-+ public boolean isQueued() {
-+ return this.queueTask.isQueued();
-+ }
-+
-+ @Override
-+ public boolean cancel() {
-+ return this.queueTask.cancel();
-+ }
-+
-+ @Override
-+ public boolean execute() {
-+ return this.queueTask.execute();
-+ }
-+
-+ @Override
-+ public Priority getPriority() {
-+ return this.queueTask.getPriority();
-+ }
-+
-+ @Override
-+ public boolean setPriority(final Priority priority) {
-+ return this.queueTask.setPriority(priority);
-+ }
-+
-+ @Override
-+ public boolean raisePriority(final Priority priority) {
-+ return this.queueTask.raisePriority(priority);
-+ }
-+
-+ @Override
-+ public boolean lowerPriority(final Priority priority) {
-+ return this.queueTask.lowerPriority(priority);
-+ }
-+
-+ @Override
-+ public long getSubOrder() {
-+ return this.queueTask.getSubOrder();
-+ }
-+
-+ @Override
-+ public boolean setSubOrder(final long subOrder) {
-+ return this.queueTask.setSubOrder(subOrder);
-+ }
-+
-+ @Override
-+ public boolean raiseSubOrder(final long subOrder) {
-+ return this.queueTask.raiseSubOrder(subOrder);
-+ }
-+
-+ @Override
-+ public boolean lowerSubOrder(final long subOrder) {
-+ return this.queueTask.lowerSubOrder(subOrder);
-+ }
-+
-+ @Override
-+ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) {
-+ return this.queueTask.setPriorityAndSubOrder(priority, subOrder);
-+ }
-+ }
-+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..cb9df914a9a6d0d3f58fa58d8c93f4f583416cd1
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/thread/PrioritisedThreadPool.java
-@@ -0,0 +1,741 @@
-+package ca.spottedleaf.concurrentutil.executor.thread;
-+
-+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
-+import ca.spottedleaf.concurrentutil.executor.queue.PrioritisedTaskQueue;
-+import ca.spottedleaf.concurrentutil.util.Priority;
-+import ca.spottedleaf.concurrentutil.util.TimeUtil;
-+import org.slf4j.Logger;
-+import org.slf4j.LoggerFactory;
-+import java.lang.reflect.Array;
-+import java.util.Arrays;
-+import java.util.concurrent.atomic.AtomicBoolean;
-+import java.util.concurrent.atomic.AtomicLong;
-+import java.util.function.Consumer;
-+
-+public final class PrioritisedThreadPool {
-+
-+ private static final Logger LOGGER = LoggerFactory.getLogger(PrioritisedThreadPool.class);
-+
-+ private final Consumer<Thread> threadModifier;
-+ private final COWArrayList<ExecutorGroup> executors = new COWArrayList<>(ExecutorGroup.class);
-+ private final COWArrayList<PrioritisedThread> threads = new COWArrayList<>(PrioritisedThread.class);
-+ private final COWArrayList<PrioritisedThread> aliveThreads = new COWArrayList<>(PrioritisedThread.class);
-+
-+ private static final Priority HIGH_PRIORITY_NOTIFY_THRESHOLD = Priority.HIGH;
-+ private static final Priority QUEUE_SHUTDOWN_PRIORITY = Priority.HIGH;
-+
-+ private boolean shutdown;
-+
-+ public PrioritisedThreadPool(final Consumer<Thread> threadModifier) {
-+ this.threadModifier = threadModifier;
-+
-+ if (threadModifier == null) {
-+ throw new NullPointerException("Thread factory may not be null");
-+ }
-+ }
-+
-+ public Thread[] getAliveThreads() {
-+ final PrioritisedThread[] threads = this.aliveThreads.getArray();
-+
-+ return Arrays.copyOf(threads, threads.length, Thread[].class);
-+ }
-+
-+ public Thread[] getCoreThreads() {
-+ final PrioritisedThread[] threads = this.threads.getArray();
-+
-+ return Arrays.copyOf(threads, threads.length, Thread[].class);
-+ }
-+
-+ /**
-+ * Prevents creation of new queues, shutdowns all non-shutdown queues if specified
-+ */
-+ public void halt(final boolean shutdownQueues) {
-+ synchronized (this) {
-+ this.shutdown = true;
-+ }
-+
-+ if (shutdownQueues) {
-+ for (final ExecutorGroup group : this.executors.getArray()) {
-+ for (final ExecutorGroup.ThreadPoolExecutor executor : group.executors.getArray()) {
-+ executor.shutdown();
-+ }
-+ }
-+ }
-+
-+ for (final PrioritisedThread thread : this.threads.getArray()) {
-+ thread.halt(false);
-+ }
-+ }
-+
-+ /**
-+ * Waits until all threads in this pool have shutdown, or until the specified time has passed.
-+ * @param msToWait Maximum time to wait.
-+ * @return {@code false} if the maximum time passed, {@code true} otherwise.
-+ */
-+ public boolean join(final long msToWait) {
-+ try {
-+ return this.join(msToWait, false);
-+ } catch (final InterruptedException ex) {
-+ throw new IllegalStateException(ex);
-+ }
-+ }
-+
-+ /**
-+ * Waits until all threads in this pool have shutdown, or until the specified time has passed.
-+ * @param msToWait Maximum time to wait.
-+ * @return {@code false} if the maximum time passed, {@code true} otherwise.
-+ * @throws InterruptedException If this thread is interrupted.
-+ */
-+ public boolean joinInterruptable(final long msToWait) throws InterruptedException {
-+ return this.join(msToWait, true);
-+ }
-+
-+ protected final boolean join(final long msToWait, final boolean interruptable) throws InterruptedException {
-+ final long nsToWait = msToWait * (1000 * 1000);
-+ final long start = System.nanoTime();
-+ final long deadline = start + nsToWait;
-+ boolean interrupted = false;
-+ try {
-+ for (final PrioritisedThread thread : this.aliveThreads.getArray()) {
-+ for (;;) {
-+ if (!thread.isAlive()) {
-+ break;
-+ }
-+ final long current = System.nanoTime();
-+ if (current >= deadline && msToWait > 0L) {
-+ return false;
-+ }
-+
-+ try {
-+ thread.join(msToWait <= 0L ? 0L : Math.max(1L, (deadline - current) / (1000 * 1000)));
-+ } catch (final InterruptedException ex) {
-+ if (interruptable) {
-+ throw ex;
-+ }
-+ interrupted = true;
-+ }
-+ }
-+ }
-+
-+ return true;
-+ } finally {
-+ if (interrupted) {
-+ Thread.currentThread().interrupt();
-+ }
-+ }
-+ }
-+
-+ /**
-+ * Shuts down this thread pool, optionally waiting for all tasks to be executed.
-+ * This function will invoke {@link PrioritisedExecutor#shutdown()} on all created executors on this
-+ * thread pool.
-+ * @param wait Whether to wait for tasks to be executed
-+ */
-+ public void shutdown(final boolean wait) {
-+ synchronized (this) {
-+ this.shutdown = true;
-+ }
-+
-+ for (final ExecutorGroup group : this.executors.getArray()) {
-+ for (final ExecutorGroup.ThreadPoolExecutor executor : group.executors.getArray()) {
-+ executor.shutdown();
-+ }
-+ }
-+
-+
-+ for (final PrioritisedThread thread : this.threads.getArray()) {
-+ // none of these can be true or else NPE
-+ thread.close(false, false);
-+ }
-+
-+ if (wait) {
-+ this.join(0L);
-+ }
-+ }
-+
-+ private void die(final PrioritisedThread thread) {
-+ this.aliveThreads.remove(thread);
-+ }
-+
-+ public void adjustThreadCount(final int threads) {
-+ synchronized (this) {
-+ if (this.shutdown) {
-+ return;
-+ }
-+
-+ final PrioritisedThread[] currentThreads = this.threads.getArray();
-+ if (threads == currentThreads.length) {
-+ // no adjustment needed
-+ return;
-+ }
-+
-+ if (threads < currentThreads.length) {
-+ // we need to trim threads
-+ for (int i = 0, difference = currentThreads.length - threads; i < difference; ++i) {
-+ final PrioritisedThread remove = currentThreads[currentThreads.length - i - 1];
-+
-+ remove.halt(false);
-+ this.threads.remove(remove);
-+ }
-+ } else {
-+ // we need to add threads
-+ for (int i = 0, difference = threads - currentThreads.length; i < difference; ++i) {
-+ final PrioritisedThread thread = new PrioritisedThread();
-+
-+ this.threadModifier.accept(thread);
-+ this.aliveThreads.add(thread);
-+ this.threads.add(thread);
-+
-+ thread.start();
-+ }
-+ }
-+ }
-+ }
-+
-+ private static int compareInsideGroup(final ExecutorGroup.ThreadPoolExecutor src, final Priority srcPriority,
-+ final ExecutorGroup.ThreadPoolExecutor dst, final Priority dstPriority) {
-+ final int priorityCompare = srcPriority.ordinal() - dstPriority.ordinal();
-+ if (priorityCompare != 0) {
-+ return priorityCompare;
-+ }
-+
-+ final int parallelismCompare = src.currentParallelism - dst.currentParallelism;
-+ if (parallelismCompare != 0) {
-+ return parallelismCompare;
-+ }
-+
-+ return TimeUtil.compareTimes(src.lastRetrieved, dst.lastRetrieved);
-+ }
-+
-+ private static int compareOutsideGroup(final ExecutorGroup.ThreadPoolExecutor src, final Priority srcPriority,
-+ final ExecutorGroup.ThreadPoolExecutor dst, final Priority dstPriority) {
-+ if (src.getGroup().division == dst.getGroup().division) {
-+ // can only compare priorities inside the same division
-+ final int priorityCompare = srcPriority.ordinal() - dstPriority.ordinal();
-+ if (priorityCompare != 0) {
-+ return priorityCompare;
-+ }
-+ }
-+
-+ final int parallelismCompare = src.getGroup().currentParallelism - dst.getGroup().currentParallelism;
-+ if (parallelismCompare != 0) {
-+ return parallelismCompare;
-+ }
-+
-+ return TimeUtil.compareTimes(src.lastRetrieved, dst.lastRetrieved);
-+ }
-+
-+ private ExecutorGroup.ThreadPoolExecutor obtainQueue() {
-+ final long time = System.nanoTime();
-+ synchronized (this) {
-+ ExecutorGroup.ThreadPoolExecutor ret = null;
-+ Priority retPriority = null;
-+
-+ for (final ExecutorGroup executorGroup : this.executors.getArray()) {
-+ ExecutorGroup.ThreadPoolExecutor highest = null;
-+ Priority highestPriority = null;
-+ for (final ExecutorGroup.ThreadPoolExecutor executor : executorGroup.executors.getArray()) {
-+ final int maxParallelism = executor.maxParallelism;
-+ if (maxParallelism > 0 && executor.currentParallelism >= maxParallelism) {
-+ continue;
-+ }
-+
-+ final Priority priority = executor.getTargetPriority();
-+
-+ if (priority == null) {
-+ continue;
-+ }
-+
-+ if (highestPriority == null || compareInsideGroup(highest, highestPriority, executor, priority) > 0) {
-+ highest = executor;
-+ highestPriority = priority;
-+ }
-+ }
-+
-+ if (highest == null) {
-+ continue;
-+ }
-+
-+ if (ret == null || compareOutsideGroup(ret, retPriority, highest, highestPriority) > 0) {
-+ ret = highest;
-+ retPriority = highestPriority;
-+ }
-+ }
-+
-+ if (ret != null) {
-+ ret.lastRetrieved = time;
-+ ++ret.currentParallelism;
-+ ++ret.getGroup().currentParallelism;
-+ return ret;
-+ }
-+
-+ return ret;
-+ }
-+ }
-+
-+ private void returnQueue(final ExecutorGroup.ThreadPoolExecutor executor) {
-+ synchronized (this) {
-+ --executor.currentParallelism;
-+ --executor.getGroup().currentParallelism;
-+ }
-+
-+ if (executor.isShutdown() && executor.queue.hasNoScheduledTasks()) {
-+ executor.getGroup().executors.remove(executor);
-+ }
-+ }
-+
-+ private void notifyAllThreads() {
-+ for (final PrioritisedThread thread : this.threads.getArray()) {
-+ thread.notifyTasks();
-+ }
-+ }
-+
-+ public ExecutorGroup createExecutorGroup(final int division, final int flags) {
-+ synchronized (this) {
-+ if (this.shutdown) {
-+ throw new IllegalStateException("Queue is shutdown: " + this.toString());
-+ }
-+
-+ final ExecutorGroup ret = new ExecutorGroup(division, flags);
-+
-+ this.executors.add(ret);
-+
-+ return ret;
-+ }
-+ }
-+
-+ private final class PrioritisedThread extends PrioritisedQueueExecutorThread {
-+
-+ private final AtomicBoolean alertedHighPriority = new AtomicBoolean();
-+
-+ public PrioritisedThread() {
-+ super(null);
-+ }
-+
-+ public boolean alertHighPriorityExecutor() {
-+ if (!this.notifyTasks()) {
-+ if (!this.alertedHighPriority.get()) {
-+ this.alertedHighPriority.set(true);
-+ }
-+ return false;
-+ }
-+
-+ return true;
-+ }
-+
-+ private boolean isAlertedHighPriority() {
-+ return this.alertedHighPriority.get() && this.alertedHighPriority.getAndSet(false);
-+ }
-+
-+ @Override
-+ protected void die() {
-+ PrioritisedThreadPool.this.die(this);
-+ }
-+
-+ @Override
-+ protected boolean pollTasks() {
-+ boolean ret = false;
-+
-+ for (;;) {
-+ if (this.halted) {
-+ break;
-+ }
-+
-+ final ExecutorGroup.ThreadPoolExecutor executor = PrioritisedThreadPool.this.obtainQueue();
-+ if (executor == null) {
-+ break;
-+ }
-+ final long deadline = System.nanoTime() + executor.queueMaxHoldTime;
-+ do {
-+ try {
-+ if (this.halted || executor.halt) {
-+ break;
-+ }
-+ if (!executor.executeTask()) {
-+ // no more tasks, try next queue
-+ break;
-+ }
-+ ret = true;
-+ } catch (final Throwable throwable) {
-+ LOGGER.error("Exception thrown from thread '" + this.getName() + "' in queue '" + executor.toString() + "'", throwable);
-+ }
-+ } while (!this.isAlertedHighPriority() && System.nanoTime() <= deadline);
-+
-+ PrioritisedThreadPool.this.returnQueue(executor);
-+ }
-+
-+
-+ return ret;
-+ }
-+ }
-+
-+ public final class ExecutorGroup {
-+
-+ private final AtomicLong subOrderGenerator = new AtomicLong();
-+ private final COWArrayList<ThreadPoolExecutor> executors = new COWArrayList<>(ThreadPoolExecutor.class);
-+
-+ private final int division;
-+ private int currentParallelism;
-+
-+ private ExecutorGroup(final int division, final int flags) {
-+ this.division = division;
-+ }
-+
-+ public ThreadPoolExecutor[] getAllExecutors() {
-+ return this.executors.getArray().clone();
-+ }
-+
-+ private PrioritisedThreadPool getThreadPool() {
-+ return PrioritisedThreadPool.this;
-+ }
-+
-+ public ThreadPoolExecutor createExecutor(final int maxParallelism, final long queueMaxHoldTime, final int flags) {
-+ synchronized (PrioritisedThreadPool.this) {
-+ if (PrioritisedThreadPool.this.shutdown) {
-+ throw new IllegalStateException("Queue is shutdown: " + PrioritisedThreadPool.this.toString());
-+ }
-+
-+ final ThreadPoolExecutor ret = new ThreadPoolExecutor(maxParallelism, queueMaxHoldTime, flags);
-+
-+ this.executors.add(ret);
-+
-+ return ret;
-+ }
-+ }
-+
-+ public final class ThreadPoolExecutor implements PrioritisedExecutor {
-+
-+ private final PrioritisedTaskQueue queue = new PrioritisedTaskQueue();
-+
-+ private volatile int maxParallelism;
-+ private final long queueMaxHoldTime;
-+ private volatile int currentParallelism;
-+ private volatile boolean halt;
-+ private long lastRetrieved = System.nanoTime();
-+
-+ private ThreadPoolExecutor(final int maxParallelism, final long queueMaxHoldTime, final int flags) {
-+ this.maxParallelism = maxParallelism;
-+ this.queueMaxHoldTime = queueMaxHoldTime;
-+ }
-+
-+ private ExecutorGroup getGroup() {
-+ return ExecutorGroup.this;
-+ }
-+
-+ private boolean canNotify() {
-+ if (this.halt) {
-+ return false;
-+ }
-+
-+ final int max = this.maxParallelism;
-+ return max < 0 || this.currentParallelism < max;
-+ }
-+
-+ private void notifyHighPriority() {
-+ if (!this.canNotify()) {
-+ return;
-+ }
-+ for (final PrioritisedThread thread : this.getGroup().getThreadPool().threads.getArray()) {
-+ if (thread.alertHighPriorityExecutor()) {
-+ return;
-+ }
-+ }
-+ }
-+
-+ private void notifyScheduled() {
-+ if (!this.canNotify()) {
-+ return;
-+ }
-+ for (final PrioritisedThread thread : this.getGroup().getThreadPool().threads.getArray()) {
-+ if (thread.notifyTasks()) {
-+ return;
-+ }
-+ }
-+ }
-+
-+ /**
-+ * Removes this queue from the thread pool without shutting the queue down or waiting for queued tasks to be executed
-+ */
-+ public void halt() {
-+ this.halt = true;
-+
-+ ExecutorGroup.this.executors.remove(this);
-+ }
-+
-+ /**
-+ * Returns whether this executor is scheduled to run tasks or is running tasks, otherwise it returns whether
-+ * this queue is not halted and not shutdown.
-+ */
-+ public boolean isActive() {
-+ if (this.halt) {
-+ return this.currentParallelism > 0;
-+ } else {
-+ if (!this.isShutdown()) {
-+ return true;
-+ }
-+
-+ return !this.queue.hasNoScheduledTasks();
-+ }
-+ }
-+
-+ @Override
-+ public boolean shutdown() {
-+ if (!this.queue.shutdown()) {
-+ return false;
-+ }
-+
-+ if (this.queue.hasNoScheduledTasks()) {
-+ ExecutorGroup.this.executors.remove(this);
-+ }
-+
-+ return true;
-+ }
-+
-+ @Override
-+ public boolean isShutdown() {
-+ return this.queue.isShutdown();
-+ }
-+
-+ public void setMaxParallelism(final int maxParallelism) {
-+ this.maxParallelism = maxParallelism;
-+ // assume that we could have increased the parallelism
-+ if (this.getTargetPriority() != null) {
-+ ExecutorGroup.this.getThreadPool().notifyAllThreads();
-+ }
-+ }
-+
-+ Priority getTargetPriority() {
-+ final Priority ret = this.queue.getHighestPriority();
-+ if (!this.isShutdown()) {
-+ return ret;
-+ }
-+
-+ return ret == null ? QUEUE_SHUTDOWN_PRIORITY : Priority.max(ret, QUEUE_SHUTDOWN_PRIORITY);
-+ }
-+
-+ @Override
-+ public long getTotalTasksScheduled() {
-+ return this.queue.getTotalTasksScheduled();
-+ }
-+
-+ @Override
-+ public long getTotalTasksExecuted() {
-+ return this.queue.getTotalTasksExecuted();
-+ }
-+
-+ @Override
-+ public long generateNextSubOrder() {
-+ return ExecutorGroup.this.subOrderGenerator.getAndIncrement();
-+ }
-+
-+ @Override
-+ public boolean executeTask() {
-+ return this.queue.executeTask();
-+ }
-+
-+ @Override
-+ public PrioritisedTask queueTask(final Runnable task) {
-+ final PrioritisedTask ret = this.createTask(task);
-+
-+ ret.queue();
-+
-+ return ret;
-+ }
-+
-+ @Override
-+ public PrioritisedTask queueTask(final Runnable task, final Priority priority) {
-+ final PrioritisedTask ret = this.createTask(task, priority);
-+
-+ ret.queue();
-+
-+ return ret;
-+ }
-+
-+ @Override
-+ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) {
-+ final PrioritisedTask ret = this.createTask(task, priority, subOrder);
-+
-+ ret.queue();
-+
-+ return ret;
-+ }
-+
-+ @Override
-+ public PrioritisedTask createTask(final Runnable task) {
-+ return this.createTask(task, Priority.NORMAL);
-+ }
-+
-+ @Override
-+ public PrioritisedTask createTask(final Runnable task, final Priority priority) {
-+ return this.createTask(task, priority, this.generateNextSubOrder());
-+ }
-+
-+ @Override
-+ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) {
-+ return new WrappedTask(this.queue.createTask(task, priority, subOrder));
-+ }
-+
-+ private final class WrappedTask implements PrioritisedTask {
-+
-+ private final PrioritisedTask wrapped;
-+
-+ private WrappedTask(final PrioritisedTask wrapped) {
-+ this.wrapped = wrapped;
-+ }
-+
-+ @Override
-+ public PrioritisedExecutor getExecutor() {
-+ return ThreadPoolExecutor.this;
-+ }
-+
-+ @Override
-+ public boolean queue() {
-+ if (this.wrapped.queue()) {
-+ final Priority priority = this.getPriority();
-+ if (priority != Priority.COMPLETING) {
-+ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) {
-+ ThreadPoolExecutor.this.notifyHighPriority();
-+ } else {
-+ ThreadPoolExecutor.this.notifyScheduled();
-+ }
-+ }
-+ return true;
-+ }
-+
-+ return false;
-+ }
-+
-+ @Override
-+ public boolean isQueued() {
-+ return this.wrapped.isQueued();
-+ }
-+
-+ @Override
-+ public boolean cancel() {
-+ return this.wrapped.cancel();
-+ }
-+
-+ @Override
-+ public boolean execute() {
-+ return this.wrapped.execute();
-+ }
-+
-+ @Override
-+ public Priority getPriority() {
-+ return this.wrapped.getPriority();
-+ }
-+
-+ @Override
-+ public boolean setPriority(final Priority priority) {
-+ if (this.wrapped.setPriority(priority)) {
-+ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) {
-+ ThreadPoolExecutor.this.notifyHighPriority();
-+ }
-+ return true;
-+ }
-+
-+ return false;
-+ }
-+
-+ @Override
-+ public boolean raisePriority(final Priority priority) {
-+ if (this.wrapped.raisePriority(priority)) {
-+ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) {
-+ ThreadPoolExecutor.this.notifyHighPriority();
-+ }
-+ return true;
-+ }
-+
-+ return false;
-+ }
-+
-+ @Override
-+ public boolean lowerPriority(final Priority priority) {
-+ return this.wrapped.lowerPriority(priority);
-+ }
-+
-+ @Override
-+ public long getSubOrder() {
-+ return this.wrapped.getSubOrder();
-+ }
-+
-+ @Override
-+ public boolean setSubOrder(final long subOrder) {
-+ return this.wrapped.setSubOrder(subOrder);
-+ }
-+
-+ @Override
-+ public boolean raiseSubOrder(final long subOrder) {
-+ return this.wrapped.raiseSubOrder(subOrder);
-+ }
-+
-+ @Override
-+ public boolean lowerSubOrder(final long subOrder) {
-+ return this.wrapped.lowerSubOrder(subOrder);
-+ }
-+
-+ @Override
-+ public boolean setPriorityAndSubOrder(final Priority priority, final long subOrder) {
-+ if (this.wrapped.setPriorityAndSubOrder(priority, subOrder)) {
-+ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) {
-+ ThreadPoolExecutor.this.notifyHighPriority();
-+ }
-+ return true;
-+ }
-+
-+ return false;
-+ }
-+ }
-+ }
-+ }
-+
-+ private static final class COWArrayList<E> {
-+
-+ private volatile E[] array;
-+
-+ public COWArrayList(final Class<E> clazz) {
-+ this.array = (E[])Array.newInstance(clazz, 0);
-+ }
-+
-+ public E[] getArray() {
-+ return this.array;
-+ }
-+
-+ public void add(final E element) {
-+ synchronized (this) {
-+ final E[] array = this.array;
-+
-+ final E[] copy = Arrays.copyOf(array, array.length + 1);
-+ copy[array.length] = element;
-+
-+ this.array = copy;
-+ }
-+ }
-+
-+ public boolean remove(final E element) {
-+ synchronized (this) {
-+ final E[] array = this.array;
-+ int index = -1;
-+ for (int i = 0, len = array.length; i < len; ++i) {
-+ if (array[i] == element) {
-+ index = i;
-+ break;
-+ }
-+ }
-+
-+ if (index == -1) {
-+ return false;
-+ }
-+
-+ final E[] copy = (E[])Array.newInstance(array.getClass().getComponentType(), array.length - 1);
-+
-+ System.arraycopy(array, 0, copy, 0, index);
-+ System.arraycopy(array, index + 1, copy, index, (array.length - 1) - index);
-+
-+ this.array = copy;
-+ }
-+
-+ return true;
-+ }
-+ }
-+}
-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..6918f130099e6c19e20a47bfdb54915cdd13732a
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/map/ConcurrentLong2ReferenceChainedHashTable.java
-@@ -0,0 +1,1704 @@
-+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> implements Iterable<ConcurrentLong2ReferenceChainedHashTable.TableEntry<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) {
-+ // noinspection unchecked
-+ 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
-+
-+ // noinspection unchecked
-+ 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");
-+ }
-+
-+ // noinspection unchecked
-+ 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) {
-+ // noinspection unchecked
-+ 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) {
-+ // noinspection unchecked
-+ 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) {
-+ // noinspection unchecked
-+ 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) {
-+ // noinspection unchecked
-+ 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) {
-+ // noinspection unchecked
-+ 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) {
-+ // noinspection unchecked
-+ 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) {
-+ // noinspection unchecked
-+ 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) {
-+ // noinspection unchecked
-+ 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) {
-+ // noinspection unchecked
-+ 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) {
-+ // noinspection unchecked
-+ 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);
-+ }
-+
-+ @Override
-+ public final Iterator<TableEntry<V>> iterator() {
-+ return this.entryIterator();
-+ }
-+
-+ /**
-+ * Returns an iterator over the keys in this map. The iterator is only guaranteed to see keys that were
-+ * added before the beginning of this call, but it may see keys added during.
-+ */
-+ 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>> {
-+
-+ public 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 {
-+
-+ public 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> {
-+
-+ public 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.lastReturned;
-+ 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 ResizeChain<V> prevChain = resizeChain.prev;
-+ this.resizeChain = prevChain;
-+ if (prevChain == null) {
-+ this.currentTable = null;
-+ return null;
-+ }
-+
-+ final TableEntry<V>[] newTable = prevChain.table;
-+
-+ // we recover the original index by modding by the new table length, as the increments applied to the index
-+ // are a multiple of the new table's length
-+ int newIdx = index & (newTable.length - 1);
-+
-+ // the increment is always the previous table's length
-+ final ResizeChain<V> nextPrevChain = prevChain.prev;
-+ final int increment;
-+ if (nextPrevChain == null) {
-+ increment = 1;
-+ } else {
-+ increment = nextPrevChain.table.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) {
-+ // noinspection unchecked
-+ 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) {
-+ // noinspection unchecked
-+ 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> {
-+
-+ public final TableEntry<V>[] table;
-+ public final ResizeChain<V> prev;
-+ public ResizeChain<V> next;
-+
-+ public ResizeChain(final TableEntry<V>[] table, final ResizeChain<V> prev, final ResizeChain<V> next) {
-+ this.table = table;
-+ this.prev = prev;
-+ this.next = next;
-+ }
-+ }
-+ }
-+
-+ public static final class TableEntry<V> {
-+
-+ private static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class);
-+
-+ private final boolean resize;
-+
-+ private final long key;
-+
-+ private volatile V value;
-+ private static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class);
-+
-+ private V getValuePlain() {
-+ //noinspection unchecked
-+ return (V)VALUE_HANDLE.get(this);
-+ }
-+
-+ private V getValueAcquire() {
-+ //noinspection unchecked
-+ return (V)VALUE_HANDLE.getAcquire(this);
-+ }
-+
-+ private V getValueVolatile() {
-+ //noinspection unchecked
-+ return (V)VALUE_HANDLE.getVolatile(this);
-+ }
-+
-+ private void setValuePlain(final V value) {
-+ VALUE_HANDLE.set(this, (Object)value);
-+ }
-+
-+ private void setValueRelease(final V value) {
-+ VALUE_HANDLE.setRelease(this, (Object)value);
-+ }
-+
-+ private void setValueVolatile(final V value) {
-+ VALUE_HANDLE.setVolatile(this, (Object)value);
-+ }
-+
-+ private volatile TableEntry<V> next;
-+ private static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class);
-+
-+ private TableEntry<V> getNextPlain() {
-+ //noinspection unchecked
-+ return (TableEntry<V>)NEXT_HANDLE.get(this);
-+ }
-+
-+ private TableEntry<V> getNextVolatile() {
-+ //noinspection unchecked
-+ return (TableEntry<V>)NEXT_HANDLE.getVolatile(this);
-+ }
-+
-+ private void setNextPlain(final TableEntry<V> next) {
-+ NEXT_HANDLE.set(this, next);
-+ }
-+
-+ private void setNextRelease(final TableEntry<V> next) {
-+ NEXT_HANDLE.setRelease(this, next);
-+ }
-+
-+ private 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..83965350d292ccf42a34520d84dcda3f88146cff
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRHashTable.java
-@@ -0,0 +1,1656 @@
-+package ca.spottedleaf.concurrentutil.map;
-+
-+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 java.lang.invoke.VarHandle;
-+import java.util.ArrayList;
-+import java.util.Arrays;
-+import java.util.Collection;
-+import java.util.Iterator;
-+import java.util.List;
-+import java.util.Map;
-+import java.util.NoSuchElementException;
-+import java.util.Set;
-+import java.util.Spliterator;
-+import java.util.Spliterators;
-+import java.util.function.BiConsumer;
-+import java.util.function.BiFunction;
-+import java.util.function.BiPredicate;
-+import java.util.function.Consumer;
-+import java.util.function.Function;
-+import java.util.function.IntFunction;
-+import java.util.function.Predicate;
-+
-+/**
-+ * <p>
-+ * Note: Not really tested, use at your own risk.
-+ * </p>
-+ * This map is safe for reading from multiple threads, however it is only safe to write from a single thread.
-+ * {@code null} keys or values are not permitted. Writes to values in this map are guaranteed to be ordered by release semantics,
-+ * however immediate visibility to other threads is not guaranteed. However, writes are guaranteed to be made visible eventually.
-+ * Reads are ordered by acquire semantics.
-+ * <p>
-+ * Iterators cannot be modified concurrently, and its backing map cannot be modified concurrently. There is no
-+ * fast-fail attempt made by iterators, thus modifying the iterator's backing map while iterating will have undefined
-+ * behaviour.
-+ * </p>
-+ * <p>
-+ * Subclasses should override {@link #clone()} to return correct instances of this class.
-+ * </p>
-+ * @param <K> {@inheritDoc}
-+ * @param <V> {@inheritDoc}
-+ */
-+public class SWMRHashTable<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>> {
-+
-+ protected int size;
-+
-+ protected TableEntry<K, V>[] table;
-+
-+ protected final float loadFactor;
-+
-+ protected static final VarHandle SIZE_HANDLE = ConcurrentUtil.getVarHandle(SWMRHashTable.class, "size", int.class);
-+ protected static final VarHandle TABLE_HANDLE = ConcurrentUtil.getVarHandle(SWMRHashTable.class, "table", TableEntry[].class);
-+
-+ /* size */
-+
-+ protected final int getSizePlain() {
-+ return (int)SIZE_HANDLE.get(this);
-+ }
-+
-+ protected final int getSizeOpaque() {
-+ return (int)SIZE_HANDLE.getOpaque(this);
-+ }
-+
-+ protected final int getSizeAcquire() {
-+ return (int)SIZE_HANDLE.getAcquire(this);
-+ }
-+
-+ protected final void setSizePlain(final int value) {
-+ SIZE_HANDLE.set(this, value);
-+ }
-+
-+ protected final void setSizeOpaque(final int value) {
-+ SIZE_HANDLE.setOpaque(this, value);
-+ }
-+
-+ protected final void setSizeRelease(final int value) {
-+ SIZE_HANDLE.setRelease(this, value);
-+ }
-+
-+ /* table */
-+
-+ protected final TableEntry<K, V>[] getTablePlain() {
-+ //noinspection unchecked
-+ return (TableEntry<K, V>[])TABLE_HANDLE.get(this);
-+ }
-+
-+ protected final TableEntry<K, V>[] getTableAcquire() {
-+ //noinspection unchecked
-+ return (TableEntry<K, V>[])TABLE_HANDLE.getAcquire(this);
-+ }
-+
-+ protected final void setTablePlain(final TableEntry<K, V>[] table) {
-+ TABLE_HANDLE.set(this, table);
-+ }
-+
-+ protected final void setTableRelease(final TableEntry<K, V>[] table) {
-+ TABLE_HANDLE.setRelease(this, table);
-+ }
-+
-+ 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;
-+
-+ /**
-+ * Constructs this map with a capacity of {@code 16} and load factor of {@code 0.75f}.
-+ */
-+ public SWMRHashTable() {
-+ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
-+ }
-+
-+ /**
-+ * Constructs this map with the specified capacity and load factor of {@code 0.75f}.
-+ * @param capacity specified initial capacity, > 0
-+ */
-+ public SWMRHashTable(final int capacity) {
-+ this(capacity, DEFAULT_LOAD_FACTOR);
-+ }
-+
-+ /**
-+ * Constructs this map with the specified capacity and load factor.
-+ * @param capacity specified capacity, > 0
-+ * @param loadFactor specified load factor, > 0 && finite
-+ */
-+ public SWMRHashTable(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);
-+ }
-+
-+ //noinspection unchecked
-+ final TableEntry<K, V>[] table = new TableEntry[tableSize];
-+ this.setTablePlain(table);
-+
-+ if (tableSize == MAXIMUM_CAPACITY) {
-+ this.threshold = -1;
-+ } else {
-+ this.threshold = getTargetCapacity(tableSize, loadFactor);
-+ }
-+
-+ this.loadFactor = loadFactor;
-+ }
-+
-+ /**
-+ * Constructs this map with a capacity of {@code 16} or the specified map's size, whichever is larger, and
-+ * with a load factor of {@code 0.75f}.
-+ * All of the specified map's entries are copied into this map.
-+ * @param other The specified map.
-+ */
-+ public SWMRHashTable(final Map<K, V> other) {
-+ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, other);
-+ }
-+
-+ /**
-+ * Constructs this map with a minimum capacity of the specified capacity or the specified map's size, whichever is larger, and
-+ * with a load factor of {@code 0.75f}.
-+ * All of the specified map's entries are copied into this map.
-+ * @param capacity specified capacity, > 0
-+ * @param other The specified map.
-+ */
-+ public SWMRHashTable(final int capacity, final Map<K, V> other) {
-+ this(capacity, DEFAULT_LOAD_FACTOR, other);
-+ }
-+
-+ /**
-+ * Constructs this map with a min capacity of the specified capacity or the specified map's size, whichever is larger, and
-+ * 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 && finite
-+ * @param other The specified map.
-+ */
-+ public SWMRHashTable(final int capacity, final float loadFactor, final Map<K, V> other) {
-+ this(Math.max(Validate.notNull(other, "Null map").size(), capacity), loadFactor);
-+ 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;
-+ }
-+
-+ 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);
-+ }
-+
-+ /** Callers must still use acquire when reading the value of the entry. */
-+ protected final TableEntry<K, V> getEntryForOpaque(final K key) {
-+ final int hash = SWMRHashTable.getHash(key);
-+ final TableEntry<K, V>[] table = this.getTableAcquire();
-+
-+ 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;
-+ }
-+ }
-+
-+ return null;
-+ }
-+
-+ protected final TableEntry<K, V> getEntryForPlain(final K key) {
-+ final int hash = SWMRHashTable.getHash(key);
-+ final TableEntry<K, V>[] table = this.getTablePlain();
-+
-+ for (TableEntry<K, V> curr = table[hash & (table.length - 1)]; curr != null; curr = curr.getNextPlain()) {
-+ if (hash == curr.hash && (key == curr.key || curr.key.equals(key))) {
-+ return curr;
-+ }
-+ }
-+
-+ return null;
-+ }
-+
-+ /* MT-Safe */
-+
-+ /** must be deterministic given a key */
-+ private static int getHash(final Object key) {
-+ int hash = key == null ? 0 : key.hashCode();
-+ return HashUtil.mix(hash);
-+ }
-+
-+ // rets -1 if capacity*loadFactor is too large
-+ protected static int getTargetCapacity(final int capacity, final float loadFactor) {
-+ final double ret = (double)capacity * (double)loadFactor;
-+ if (Double.isInfinite(ret) || ret >= ((double)Integer.MAX_VALUE)) {
-+ return -1;
-+ }
-+
-+ return (int)ret;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public boolean equals(final Object obj) {
-+ if (this == obj) {
-+ return true;
-+ }
-+ /* Make no attempt to deal with concurrent modifications */
-+ if (!(obj instanceof Map<?, ?> other)) {
-+ return false;
-+ }
-+
-+ if (this.size() != other.size()) {
-+ return false;
-+ }
-+
-+ final TableEntry<K, V>[] table = this.getTableAcquire();
-+
-+ for (int i = 0, len = table.length; i < len; ++i) {
-+ 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);
-+ if (otherValue == null || (value != otherValue && value.equals(otherValue))) {
-+ return false;
-+ }
-+ }
-+ }
-+
-+ return true;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public int hashCode() {
-+ /* Make no attempt to deal with concurrent modifications */
-+ int hash = 0;
-+ final TableEntry<K, V>[] table = this.getTableAcquire();
-+
-+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<K, V> curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
-+ hash += curr.hashCode();
-+ }
-+ }
-+
-+ return hash;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public String toString() {
-+ final StringBuilder builder = new StringBuilder(64);
-+ builder.append("SWMRHashTable:{");
-+
-+ this.forEach((final K key, final V value) -> {
-+ builder.append("{key: \"").append(key).append("\", value: \"").append(value).append("\"}");
-+ });
-+
-+ return builder.append('}').toString();
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public SWMRHashTable<K, V> clone() {
-+ return new SWMRHashTable<>(this.getTableAcquire().length, this.loadFactor, this);
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public Iterator<Map.Entry<K, V>> iterator() {
-+ return new EntryIterator<>(this.getTableAcquire(), this);
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ 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 = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
-+ action.accept(curr);
-+ }
-+ }
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public void forEach(final BiConsumer<? super K, ? super 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 = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
-+ final V value = curr.getValueAcquire();
-+
-+ action.accept(curr.key, value);
-+ }
-+ }
-+ }
-+
-+ /**
-+ * Provides the specified consumer with all keys contained within this map.
-+ * @param action The specified consumer.
-+ */
-+ public void forEachKey(final Consumer<? super K> 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 = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
-+ action.accept(curr.key);
-+ }
-+ }
-+ }
-+
-+ /**
-+ * Provides the specified consumer with all values contained within this map. Equivalent to {@code map.values().forEach(Consumer)}.
-+ * @param action The specified consumer.
-+ */
-+ public void forEachValue(final Consumer<? super 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 = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
-+ final V value = curr.getValueAcquire();
-+
-+ action.accept(value);
-+ }
-+ }
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public V get(final Object key) {
-+ Validate.notNull(key, "Null key");
-+
-+ //noinspection unchecked
-+ final TableEntry<K, V> entry = this.getEntryForOpaque((K)key);
-+ return entry == null ? null : entry.getValueAcquire();
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public boolean containsKey(final Object key) {
-+ Validate.notNull(key, "Null key");
-+
-+ // note: we need to use getValueAcquire, so that the reads from this map are ordered by acquire semantics
-+ return this.get(key) != null;
-+ }
-+
-+ /**
-+ * Returns {@code true} if this map contains an entry with the specified key and value at some point during this call.
-+ * @param key The specified key.
-+ * @param value The specified value.
-+ * @return {@code true} if this map contains an entry with the specified key and value.
-+ */
-+ public boolean contains(final Object key, final Object value) {
-+ Validate.notNull(key, "Null key");
-+
-+ //noinspection unchecked
-+ final TableEntry<K, V> entry = this.getEntryForOpaque((K)key);
-+
-+ if (entry == null) {
-+ return false;
-+ }
-+
-+ final V entryVal = entry.getValueAcquire();
-+ return entryVal == value || entryVal.equals(value);
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public boolean containsValue(final Object value) {
-+ Validate.notNull(value, "Null value");
-+
-+ final TableEntry<K, V>[] table = this.getTableAcquire();
-+ for (int i = 0, len = table.length; i < len; ++i) {
-+ 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;
-+ }
-+ }
-+ }
-+
-+ return false;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public V getOrDefault(final Object key, final V defaultValue) {
-+ Validate.notNull(key, "Null key");
-+
-+ //noinspection unchecked
-+ final TableEntry<K, V> entry = this.getEntryForOpaque((K)key);
-+
-+ return entry == null ? defaultValue : entry.getValueAcquire();
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public int size() {
-+ return this.getSizeAcquire();
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public boolean isEmpty() {
-+ return this.getSizeAcquire() == 0;
-+ }
-+
-+ protected KeySet<K, V> keyset;
-+ protected ValueCollection<K, V> values;
-+ protected EntrySet<K, V> entrySet;
-+
-+ @Override
-+ public Set<K> keySet() {
-+ return this.keyset == null ? this.keyset = new KeySet<>(this) : this.keyset;
-+ }
-+
-+ @Override
-+ public Collection<V> values() {
-+ return this.values == null ? this.values = new ValueCollection<>(this) : this.values;
-+ }
-+
-+ @Override
-+ public Set<Map.Entry<K, V>> entrySet() {
-+ return this.entrySet == null ? this.entrySet = new EntrySet<>(this) : this.entrySet;
-+ }
-+
-+ /* Non-MT-Safe */
-+
-+ protected int threshold;
-+
-+ protected final void checkResize(final int minCapacity) {
-+ if (minCapacity <= this.threshold || this.threshold < 0) {
-+ return;
-+ }
-+
-+ final TableEntry<K, V>[] table = this.getTablePlain();
-+ int newCapacity = minCapacity >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : IntegerUtil.roundCeilLog2(minCapacity);
-+ if (newCapacity < 0) {
-+ newCapacity = MAXIMUM_CAPACITY;
-+ }
-+ if (newCapacity <= table.length) {
-+ if (newCapacity == MAXIMUM_CAPACITY) {
-+ return;
-+ }
-+ newCapacity = table.length << 1;
-+ }
-+
-+ //noinspection unchecked
-+ final TableEntry<K, V>[] newTable = new TableEntry[newCapacity];
-+ final int indexMask = newCapacity - 1;
-+
-+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<K, V> entry = table[i]; entry != null; entry = entry.getNextPlain()) {
-+ final int hash = entry.hash;
-+ final int index = hash & indexMask;
-+
-+ /* we need to create a new entry since there could be reading threads */
-+ final TableEntry<K, V> insert = new TableEntry<>(hash, entry.key, entry.getValuePlain());
-+
-+ final TableEntry<K, V> prev = newTable[index];
-+
-+ newTable[index] = insert;
-+ insert.setNextPlain(prev);
-+ }
-+ }
-+
-+ if (newCapacity == MAXIMUM_CAPACITY) {
-+ this.threshold = -1; /* No more resizing */
-+ } else {
-+ this.threshold = getTargetCapacity(newCapacity, this.loadFactor);
-+ }
-+ this.setTableRelease(newTable); /* use release to publish entries in table */
-+ }
-+
-+ protected final int addToSize(final int num) {
-+ final int newSize = this.getSizePlain() + num;
-+
-+ this.setSizeOpaque(newSize);
-+ this.checkResize(newSize);
-+
-+ return newSize;
-+ }
-+
-+ protected final int removeFromSize(final int num) {
-+ final int newSize = this.getSizePlain() - num;
-+
-+ this.setSizeOpaque(newSize);
-+
-+ return newSize;
-+ }
-+
-+ /* Cannot be used to perform downsizing */
-+ protected final int removeFromSizePlain(final int num) {
-+ final int newSize = this.getSizePlain() - num;
-+
-+ this.setSizePlain(newSize);
-+
-+ return newSize;
-+ }
-+
-+ protected final V put(final K key, final V value, final boolean onlyIfAbsent) {
-+ final TableEntry<K, V>[] table = this.getTablePlain();
-+ final int hash = SWMRHashTable.getHash(key);
-+ final int index = hash & (table.length - 1);
-+
-+ final TableEntry<K, V> head = table[index];
-+ if (head == null) {
-+ final TableEntry<K, V> insert = new TableEntry<>(hash, key, value);
-+ setAtIndexRelease(table, index, insert);
-+ this.addToSize(1);
-+ return null;
-+ }
-+
-+ for (TableEntry<K, V> curr = head;;) {
-+ if (curr.hash == hash && (key == curr.key || curr.key.equals(key))) {
-+ if (onlyIfAbsent) {
-+ return curr.getValuePlain();
-+ }
-+
-+ final V currVal = curr.getValuePlain();
-+ curr.setValueRelease(value);
-+ return currVal;
-+ }
-+
-+ final TableEntry<K, V> next = curr.getNextPlain();
-+ if (next != null) {
-+ curr = next;
-+ continue;
-+ }
-+
-+ final TableEntry<K, V> insert = new TableEntry<>(hash, key, value);
-+
-+ curr.setNextRelease(insert);
-+ this.addToSize(1);
-+ return null;
-+ }
-+ }
-+
-+ /**
-+ * Removes a key-value pair from this map if the specified predicate returns true. The specified predicate is
-+ * tested with every entry in this map. Returns the number of key-value pairs removed.
-+ * @param predicate The predicate to test key-value pairs against.
-+ * @return The total number of key-value pairs removed from this map.
-+ */
-+ public int removeIf(final BiPredicate<K, V> predicate) {
-+ Validate.notNull(predicate, "Null predicate");
-+
-+ int removed = 0;
-+
-+ final TableEntry<K, V>[] table = this.getTablePlain();
-+
-+ bin_iteration_loop:
-+ for (int i = 0, len = table.length; i < len; ++i) {
-+ TableEntry<K, V> curr = table[i];
-+ if (curr == null) {
-+ continue;
-+ }
-+
-+ /* Handle bin nodes first */
-+ while (predicate.test(curr.key, curr.getValuePlain())) {
-+ ++removed;
-+ this.removeFromSizePlain(1); /* required in case predicate throws an exception */
-+
-+ setAtIndexRelease(table, i, curr = curr.getNextPlain());
-+
-+ if (curr == null) {
-+ continue bin_iteration_loop;
-+ }
-+ }
-+
-+ TableEntry<K, V> prev;
-+
-+ /* curr at this point is the bin node */
-+
-+ for (prev = curr, curr = curr.getNextPlain(); curr != null;) {
-+ /* If we want to remove, then we should hold prev, as it will be a valid entry to link on */
-+ if (predicate.test(curr.key, curr.getValuePlain())) {
-+ ++removed;
-+ this.removeFromSizePlain(1); /* required in case predicate throws an exception */
-+
-+ prev.setNextRelease(curr = curr.getNextPlain());
-+ } else {
-+ prev = curr;
-+ curr = curr.getNextPlain();
-+ }
-+ }
-+ }
-+
-+ return removed;
-+ }
-+
-+ /**
-+ * Removes a key-value pair from this map if the specified predicate returns true. The specified predicate is
-+ * tested with every entry in this map. Returns the number of key-value pairs removed.
-+ * @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 Map.Entry<K, V>> predicate) {
-+ Validate.notNull(predicate, "Null predicate");
-+
-+ int removed = 0;
-+
-+ final TableEntry<K, V>[] table = this.getTablePlain();
-+
-+ bin_iteration_loop:
-+ for (int i = 0, len = table.length; i < len; ++i) {
-+ TableEntry<K, V> curr = table[i];
-+ if (curr == null) {
-+ continue;
-+ }
-+
-+ /* Handle bin nodes first */
-+ while (predicate.test(curr)) {
-+ ++removed;
-+ this.removeFromSizePlain(1); /* required in case predicate throws an exception */
-+
-+ setAtIndexRelease(table, i, curr = curr.getNextPlain());
-+
-+ if (curr == null) {
-+ continue bin_iteration_loop;
-+ }
-+ }
-+
-+ TableEntry<K, V> prev;
-+
-+ /* curr at this point is the bin node */
-+
-+ for (prev = curr, curr = curr.getNextPlain(); curr != null;) {
-+ /* If we want to remove, then we should hold prev, as it will be a valid entry to link on */
-+ if (predicate.test(curr)) {
-+ ++removed;
-+ this.removeFromSizePlain(1); /* required in case predicate throws an exception */
-+
-+ prev.setNextRelease(curr = curr.getNextPlain());
-+ } else {
-+ prev = curr;
-+ curr = curr.getNextPlain();
-+ }
-+ }
-+ }
-+
-+ return removed;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public V put(final K key, final V value) {
-+ Validate.notNull(key, "Null key");
-+ Validate.notNull(value, "Null value");
-+
-+ return this.put(key, value, false);
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public V putIfAbsent(final K key, final V value) {
-+ Validate.notNull(key, "Null key");
-+ Validate.notNull(value, "Null value");
-+
-+ return this.put(key, value, true);
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public boolean remove(final Object key, final Object value) {
-+ Validate.notNull(key, "Null key");
-+ Validate.notNull(value, "Null value");
-+
-+ final TableEntry<K, V>[] table = this.getTablePlain();
-+ final int hash = SWMRHashTable.getHash(key);
-+ final int index = hash & (table.length - 1);
-+
-+ final TableEntry<K, V> head = table[index];
-+ if (head == null) {
-+ return false;
-+ }
-+
-+ if (head.hash == hash && (head.key == key || head.key.equals(key))) {
-+ final V currVal = head.getValuePlain();
-+
-+ if (currVal != value && !currVal.equals(value)) {
-+ return false;
-+ }
-+
-+ setAtIndexRelease(table, index, head.getNextPlain());
-+ this.removeFromSize(1);
-+
-+ return true;
-+ }
-+
-+ for (TableEntry<K, V> curr = head.getNextPlain(), prev = head; curr != null; prev = curr, curr = curr.getNextPlain()) {
-+ if (curr.hash == hash && (curr.key == key || curr.key.equals(key))) {
-+ final V currVal = curr.getValuePlain();
-+
-+ if (currVal != value && !currVal.equals(value)) {
-+ return false;
-+ }
-+
-+ prev.setNextRelease(curr.getNextPlain());
-+ this.removeFromSize(1);
-+
-+ return true;
-+ }
-+ }
-+
-+ return false;
-+ }
-+
-+ protected final V remove(final Object key, final int hash) {
-+ final TableEntry<K, V>[] table = this.getTablePlain();
-+ final int index = (table.length - 1) & hash;
-+
-+ final TableEntry<K, V> head = table[index];
-+ if (head == null) {
-+ return null;
-+ }
-+
-+ if (hash == head.hash && (head.key == key || head.key.equals(key))) {
-+ setAtIndexRelease(table, index, head.getNextPlain());
-+ this.removeFromSize(1);
-+
-+ return head.getValuePlain();
-+ }
-+
-+ for (TableEntry<K, V> curr = head.getNextPlain(), prev = head; curr != null; prev = curr, curr = curr.getNextPlain()) {
-+ if (curr.hash == hash && (key == curr.key || curr.key.equals(key))) {
-+ prev.setNextRelease(curr.getNextPlain());
-+ this.removeFromSize(1);
-+
-+ return curr.getValuePlain();
-+ }
-+ }
-+
-+ return null;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public V remove(final Object key) {
-+ Validate.notNull(key, "Null key");
-+
-+ return this.remove(key, SWMRHashTable.getHash(key));
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public boolean replace(final K key, final V oldValue, final V newValue) {
-+ Validate.notNull(key, "Null key");
-+ Validate.notNull(oldValue, "Null oldValue");
-+ Validate.notNull(newValue, "Null newValue");
-+
-+ final TableEntry<K, V> entry = this.getEntryForPlain(key);
-+ if (entry == null) {
-+ return false;
-+ }
-+
-+ final V currValue = entry.getValuePlain();
-+ if (currValue == oldValue || currValue.equals(oldValue)) {
-+ entry.setValueRelease(newValue);
-+ return true;
-+ }
-+
-+ return false;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public V replace(final K key, final V value) {
-+ Validate.notNull(key, "Null key");
-+ Validate.notNull(value, "Null value");
-+
-+ final TableEntry<K, V> entry = this.getEntryForPlain(key);
-+ if (entry == null) {
-+ return null;
-+ }
-+
-+ final V prev = entry.getValuePlain();
-+ entry.setValueRelease(value);
-+ return prev;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public void replaceAll(final BiFunction<? super K, ? super V, ? extends V> function) {
-+ Validate.notNull(function, "Null function");
-+
-+ final TableEntry<K, V>[] table = this.getTablePlain();
-+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<K, V> curr = table[i]; curr != null; curr = curr.getNextPlain()) {
-+ final V value = curr.getValuePlain();
-+
-+ final V newValue = function.apply(curr.key, value);
-+ if (newValue == null) {
-+ throw new NullPointerException();
-+ }
-+
-+ curr.setValueRelease(newValue);
-+ }
-+ }
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public void putAll(final Map<? extends K, ? extends V> map) {
-+ Validate.notNull(map, "Null map");
-+
-+ final int size = map.size();
-+ this.checkResize(Math.max(this.getSizePlain() + size/2, size)); /* preemptively resize */
-+ map.forEach(this::put);
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ * <p>
-+ * This call is non-atomic and the order that which entries are removed is undefined. The clear operation itself
-+ * is release ordered, that is, after the clear operation is performed a release fence is performed.
-+ * </p>
-+ */
-+ @Override
-+ public void clear() {
-+ Arrays.fill(this.getTablePlain(), null);
-+ this.setSizeRelease(0);
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public V compute(final K key, final BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
-+ Validate.notNull(key, "Null key");
-+ Validate.notNull(remappingFunction, "Null remappingFunction");
-+
-+ final int hash = SWMRHashTable.getHash(key);
-+ final TableEntry<K, V>[] table = this.getTablePlain();
-+ final int index = hash & (table.length - 1);
-+
-+ for (TableEntry<K, V> curr = table[index], prev = null;;prev = curr, curr = curr.getNextPlain()) {
-+ if (curr == null) {
-+ final V newVal = remappingFunction.apply(key ,null);
-+
-+ if (newVal == null) {
-+ return null;
-+ }
-+
-+ final TableEntry<K, V> insert = new TableEntry<>(hash, key, newVal);
-+ if (prev == null) {
-+ setAtIndexRelease(table, index, insert);
-+ } else {
-+ prev.setNextRelease(insert);
-+ }
-+
-+ this.addToSize(1);
-+
-+ return newVal;
-+ }
-+
-+ if (curr.hash == hash && (curr.key == key || curr.key.equals(key))) {
-+ final V newVal = remappingFunction.apply(key, curr.getValuePlain());
-+
-+ if (newVal != null) {
-+ curr.setValueRelease(newVal);
-+ return newVal;
-+ }
-+
-+ if (prev == null) {
-+ setAtIndexRelease(table, index, curr.getNextPlain());
-+ } else {
-+ prev.setNextRelease(curr.getNextPlain());
-+ }
-+
-+ this.removeFromSize(1);
-+
-+ return null;
-+ }
-+ }
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public V computeIfPresent(final K key, final BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
-+ Validate.notNull(key, "Null key");
-+ Validate.notNull(remappingFunction, "Null remappingFunction");
-+
-+ final int hash = SWMRHashTable.getHash(key);
-+ final TableEntry<K, V>[] table = this.getTablePlain();
-+ final int index = hash & (table.length - 1);
-+
-+ for (TableEntry<K, V> curr = table[index], prev = null; curr != null; prev = curr, curr = curr.getNextPlain()) {
-+ if (curr.hash != hash || (curr.key != key && !curr.key.equals(key))) {
-+ continue;
-+ }
-+
-+ final V newVal = remappingFunction.apply(key, curr.getValuePlain());
-+ if (newVal != null) {
-+ curr.setValueRelease(newVal);
-+ return newVal;
-+ }
-+
-+ if (prev == null) {
-+ setAtIndexRelease(table, index, curr.getNextPlain());
-+ } else {
-+ prev.setNextRelease(curr.getNextPlain());
-+ }
-+
-+ this.removeFromSize(1);
-+
-+ return null;
-+ }
-+
-+ return null;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public V computeIfAbsent(final K key, final Function<? super K, ? extends V> mappingFunction) {
-+ Validate.notNull(key, "Null key");
-+ Validate.notNull(mappingFunction, "Null mappingFunction");
-+
-+ final int hash = SWMRHashTable.getHash(key);
-+ final TableEntry<K, V>[] table = this.getTablePlain();
-+ final int index = hash & (table.length - 1);
-+
-+ for (TableEntry<K, V> curr = table[index], prev = null;;prev = curr, curr = curr.getNextPlain()) {
-+ if (curr != null) {
-+ if (curr.hash == hash && (curr.key == key || curr.key.equals(key))) {
-+ return curr.getValuePlain();
-+ }
-+ continue;
-+ }
-+
-+ final V newVal = mappingFunction.apply(key);
-+
-+ if (newVal == null) {
-+ return null;
-+ }
-+
-+ final TableEntry<K, V> insert = new TableEntry<>(hash, key, newVal);
-+ if (prev == null) {
-+ setAtIndexRelease(table, index, insert);
-+ } else {
-+ prev.setNextRelease(insert);
-+ }
-+
-+ this.addToSize(1);
-+
-+ return newVal;
-+ }
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public V merge(final K key, final V value, final BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
-+ Validate.notNull(key, "Null key");
-+ Validate.notNull(value, "Null value");
-+ Validate.notNull(remappingFunction, "Null remappingFunction");
-+
-+ final int hash = SWMRHashTable.getHash(key);
-+ final TableEntry<K, V>[] table = this.getTablePlain();
-+ final int index = hash & (table.length - 1);
-+
-+ for (TableEntry<K, V> curr = table[index], prev = null;;prev = curr, curr = curr.getNextPlain()) {
-+ if (curr == null) {
-+ final TableEntry<K, V> insert = new TableEntry<>(hash, key, value);
-+ if (prev == null) {
-+ setAtIndexRelease(table, index, insert);
-+ } else {
-+ prev.setNextRelease(insert);
-+ }
-+
-+ this.addToSize(1);
-+
-+ return value;
-+ }
-+
-+ if (curr.hash == hash && (curr.key == key || curr.key.equals(key))) {
-+ final V newVal = remappingFunction.apply(curr.getValuePlain(), value);
-+
-+ if (newVal != null) {
-+ curr.setValueRelease(newVal);
-+ return newVal;
-+ }
-+
-+ if (prev == null) {
-+ setAtIndexRelease(table, index, curr.getNextPlain());
-+ } else {
-+ prev.setNextRelease(curr.getNextPlain());
-+ }
-+
-+ this.removeFromSize(1);
-+
-+ return null;
-+ }
-+ }
-+ }
-+
-+ 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;
-+
-+ protected TableEntry<K, V> next;
-+
-+ protected static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class);
-+ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class);
-+
-+ /* value */
-+
-+ 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 void setValueRelease(final V to) {
-+ VALUE_HANDLE.setRelease(this, to);
-+ }
-+
-+ /* next */
-+
-+ protected final TableEntry<K, V> getNextPlain() {
-+ //noinspection unchecked
-+ return (TableEntry<K, V>)NEXT_HANDLE.get(this);
-+ }
-+
-+ protected final TableEntry<K, V> getNextOpaque() {
-+ //noinspection unchecked
-+ return (TableEntry<K, V>)NEXT_HANDLE.getOpaque(this);
-+ }
-+
-+ protected final void setNextPlain(final TableEntry<K, V> next) {
-+ NEXT_HANDLE.set(this, next);
-+ }
-+
-+ protected final void setNextRelease(final TableEntry<K, V> next) {
-+ NEXT_HANDLE.setRelease(this, next);
-+ }
-+
-+ protected TableEntry(final int hash, final K key, final V value) {
-+ this.hash = hash;
-+ this.key = key;
-+ this.value = value;
-+ }
-+
-+ @Override
-+ public K getKey() {
-+ return this.key;
-+ }
-+
-+ @Override
-+ public V getValue() {
-+ return this.getValueAcquire();
-+ }
-+
-+ @Override
-+ public V setValue(final V value) {
-+ throw new UnsupportedOperationException();
-+ }
-+
-+ protected static int hash(final Object key, final Object value) {
-+ return key.hashCode() ^ (value == null ? 0 : value.hashCode());
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public int hashCode() {
-+ return hash(this.key, this.getValueAcquire());
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public boolean equals(final Object obj) {
-+ if (this == obj) {
-+ return true;
-+ }
-+
-+ if (!(obj instanceof Map.Entry<?, ?> other)) {
-+ return false;
-+ }
-+ final Object otherKey = other.getKey();
-+ final Object otherValue = other.getValue();
-+
-+ final K thisKey = this.getKey();
-+ final V thisVal = this.getValueAcquire();
-+ return (thisKey == otherKey || thisKey.equals(otherKey)) &&
-+ (thisVal == otherValue || thisVal.equals(otherValue));
-+ }
-+ }
-+
-+
-+ protected static abstract class TableEntryIterator<K, V, T> implements Iterator<T> {
-+
-+ protected final TableEntry<K, V>[] table;
-+ protected final SWMRHashTable<K, V> map;
-+
-+ /* bin which our current element resides on */
-+ protected int tableIndex;
-+
-+ protected TableEntry<K, V> currEntry; /* curr entry, null if no more to iterate or if curr was removed or if we've just init'd */
-+ protected TableEntry<K, V> nextEntry; /* may not be on the same bin as currEntry */
-+
-+ protected TableEntryIterator(final TableEntry<K, V>[] table, final SWMRHashTable<K, V> map) {
-+ this.table = table;
-+ this.map = map;
-+ int tableIndex = 0;
-+ for (int len = table.length; tableIndex < len; ++tableIndex) {
-+ final TableEntry<K, V> entry = getAtIndexOpaque(table, tableIndex);
-+ if (entry != null) {
-+ this.nextEntry = entry;
-+ this.tableIndex = tableIndex + 1;
-+ return;
-+ }
-+ }
-+ this.tableIndex = tableIndex;
-+ }
-+
-+ @Override
-+ public boolean hasNext() {
-+ return this.nextEntry != null;
-+ }
-+
-+ protected final TableEntry<K, V> advanceEntry() {
-+ final TableEntry<K, V>[] table = this.table;
-+ final int tableLength = table.length;
-+ int tableIndex = this.tableIndex;
-+ final TableEntry<K, V> curr = this.nextEntry;
-+ if (curr == null) {
-+ return null;
-+ }
-+
-+ this.currEntry = curr;
-+
-+ // set up nextEntry
-+
-+ // find next in chain
-+ TableEntry<K, V> next = curr.getNextOpaque();
-+
-+ if (next != null) {
-+ this.nextEntry = next;
-+ return curr;
-+ }
-+
-+ // nothing in chain, so find next available bin
-+ for (;tableIndex < tableLength; ++tableIndex) {
-+ next = getAtIndexOpaque(table, tableIndex);
-+ if (next != null) {
-+ this.nextEntry = next;
-+ this.tableIndex = tableIndex + 1;
-+ return curr;
-+ }
-+ }
-+
-+ this.nextEntry = null;
-+ this.tableIndex = tableIndex;
-+ return curr;
-+ }
-+
-+ @Override
-+ public void remove() {
-+ final TableEntry<K, V> curr = this.currEntry;
-+ if (curr == null) {
-+ throw new IllegalStateException();
-+ }
-+
-+ this.map.remove(curr.key, curr.hash);
-+
-+ this.currEntry = null;
-+ }
-+ }
-+
-+ protected static final class ValueIterator<K, V> extends TableEntryIterator<K, V, V> {
-+
-+ protected ValueIterator(final TableEntry<K, V>[] table, final SWMRHashTable<K, V> map) {
-+ super(table, map);
-+ }
-+
-+ @Override
-+ public V next() {
-+ final TableEntry<K, V> entry = this.advanceEntry();
-+
-+ if (entry == null) {
-+ throw new NoSuchElementException();
-+ }
-+
-+ return entry.getValueAcquire();
-+ }
-+ }
-+
-+ protected static final class KeyIterator<K, V> extends TableEntryIterator<K, V, K> {
-+
-+ protected KeyIterator(final TableEntry<K, V>[] table, final SWMRHashTable<K, V> map) {
-+ super(table, map);
-+ }
-+
-+ @Override
-+ public K next() {
-+ final TableEntry<K, V> curr = this.advanceEntry();
-+
-+ if (curr == null) {
-+ throw new NoSuchElementException();
-+ }
-+
-+ return curr.key;
-+ }
-+ }
-+
-+ protected static final class EntryIterator<K, V> extends TableEntryIterator<K, V, Map.Entry<K, V>> {
-+
-+ protected EntryIterator(final TableEntry<K, V>[] table, final SWMRHashTable<K, V> map) {
-+ super(table, map);
-+ }
-+
-+ @Override
-+ public Map.Entry<K, V> next() {
-+ final TableEntry<K, V> curr = this.advanceEntry();
-+
-+ if (curr == null) {
-+ throw new NoSuchElementException();
-+ }
-+
-+ return curr;
-+ }
-+ }
-+
-+ protected static abstract class ViewCollection<K, V, T> implements Collection<T> {
-+
-+ protected final SWMRHashTable<K, V> map;
-+
-+ protected ViewCollection(final SWMRHashTable<K, V> map) {
-+ this.map = map;
-+ }
-+
-+ @Override
-+ public boolean add(final T element) {
-+ throw new UnsupportedOperationException();
-+ }
-+
-+ @Override
-+ public boolean addAll(final Collection<? extends T> collections) {
-+ throw new UnsupportedOperationException();
-+ }
-+
-+ @Override
-+ public boolean removeAll(final Collection<?> collection) {
-+ Validate.notNull(collection, "Null collection");
-+
-+ boolean modified = false;
-+ for (final Object element : collection) {
-+ modified |= this.remove(element);
-+ }
-+ return modified;
-+ }
-+
-+ @Override
-+ public int size() {
-+ return this.map.size();
-+ }
-+
-+ @Override
-+ public boolean isEmpty() {
-+ return this.size() == 0;
-+ }
-+
-+ @Override
-+ public void clear() {
-+ this.map.clear();
-+ }
-+
-+ @Override
-+ public boolean containsAll(final Collection<?> collection) {
-+ Validate.notNull(collection, "Null collection");
-+
-+ for (final Object element : collection) {
-+ if (!this.contains(element)) {
-+ return false;
-+ }
-+ }
-+
-+ return true;
-+ }
-+
-+ @Override
-+ public Object[] toArray() {
-+ final List<T> list = new ArrayList<>(this.size());
-+
-+ this.forEach(list::add);
-+
-+ return list.toArray();
-+ }
-+
-+ @Override
-+ public <E> E[] toArray(final E[] array) {
-+ final List<T> list = new ArrayList<>(this.size());
-+
-+ this.forEach(list::add);
-+
-+ return list.toArray(array);
-+ }
-+
-+ @Override
-+ public <E> E[] toArray(final IntFunction<E[]> generator) {
-+ final List<T> list = new ArrayList<>(this.size());
-+
-+ this.forEach(list::add);
-+
-+ return list.toArray(generator);
-+ }
-+
-+ @Override
-+ public int hashCode() {
-+ int hash = 0;
-+ for (final T element : this) {
-+ hash += element == null ? 0 : element.hashCode();
-+ }
-+ return hash;
-+ }
-+
-+ @Override
-+ public Spliterator<T> spliterator() { // TODO implement
-+ return Spliterators.spliterator(this, Spliterator.NONNULL);
-+ }
-+ }
-+
-+ protected static abstract class ViewSet<K, V, T> extends ViewCollection<K, V, T> implements Set<T> {
-+
-+ protected ViewSet(final SWMRHashTable<K, V> map) {
-+ super(map);
-+ }
-+
-+ @Override
-+ public boolean equals(final Object obj) {
-+ if (this == obj) {
-+ return true;
-+ }
-+
-+ if (!(obj instanceof Set)) {
-+ return false;
-+ }
-+
-+ final Set<?> other = (Set<?>)obj;
-+ if (other.size() != this.size()) {
-+ return false;
-+ }
-+
-+ return this.containsAll(other);
-+ }
-+ }
-+
-+ protected static final class EntrySet<K, V> extends ViewSet<K, V, Map.Entry<K, V>> implements Set<Map.Entry<K, V>> {
-+
-+ protected EntrySet(final SWMRHashTable<K, V> map) {
-+ super(map);
-+ }
-+
-+ @Override
-+ public boolean remove(final Object object) {
-+ if (!(object instanceof Map.Entry<?, ?> entry)) {
-+ return false;
-+ }
-+
-+ final Object key;
-+ final Object value;
-+
-+ try {
-+ key = entry.getKey();
-+ value = entry.getValue();
-+ } catch (final IllegalStateException ex) {
-+ return false;
-+ }
-+
-+ return this.map.remove(key, value);
-+ }
-+
-+ @Override
-+ public boolean removeIf(final Predicate<? super Map.Entry<K, V>> filter) {
-+ Validate.notNull(filter, "Null filter");
-+
-+ return this.map.removeEntryIf(filter) != 0;
-+ }
-+
-+ @Override
-+ public boolean retainAll(final Collection<?> collection) {
-+ Validate.notNull(collection, "Null collection");
-+
-+ return this.map.removeEntryIf((final Map.Entry<K, V> entry) -> {
-+ return !collection.contains(entry);
-+ }) != 0;
-+ }
-+
-+ @Override
-+ public Iterator<Map.Entry<K, V>> iterator() {
-+ return new EntryIterator<>(this.map.getTableAcquire(), this.map);
-+ }
-+
-+ @Override
-+ 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<?, ?> entry)) {
-+ return false;
-+ }
-+
-+ final Object key;
-+ final Object value;
-+
-+ try {
-+ key = entry.getKey();
-+ value = entry.getValue();
-+ } catch (final IllegalStateException ex) {
-+ return false;
-+ }
-+
-+ return this.map.contains(key, value);
-+ }
-+
-+ @Override
-+ public String toString() {
-+ return CollectionUtil.toString(this, "SWMRHashTableEntrySet");
-+ }
-+ }
-+
-+ protected static final class KeySet<K, V> extends ViewSet<K, V, K> {
-+
-+ protected KeySet(final SWMRHashTable<K, V> map) {
-+ super(map);
-+ }
-+
-+ @Override
-+ public Iterator<K> iterator() {
-+ return new KeyIterator<>(this.map.getTableAcquire(), this.map);
-+ }
-+
-+ @Override
-+ public void forEach(final Consumer<? super K> action) {
-+ Validate.notNull(action, "Null action");
-+
-+ this.map.forEachKey(action);
-+ }
-+
-+ @Override
-+ public boolean contains(final Object key) {
-+ Validate.notNull(key, "Null key");
-+
-+ return this.map.containsKey(key);
-+ }
-+
-+ @Override
-+ public boolean remove(final Object key) {
-+ Validate.notNull(key, "Null key");
-+
-+ return this.map.remove(key) != null;
-+ }
-+
-+ @Override
-+ public boolean retainAll(final Collection<?> collection) {
-+ Validate.notNull(collection, "Null collection");
-+
-+ return this.map.removeIf((final K key, final V value) -> {
-+ return !collection.contains(key);
-+ }) != 0;
-+ }
-+
-+ @Override
-+ public boolean removeIf(final Predicate<? super K> filter) {
-+ Validate.notNull(filter, "Null filter");
-+
-+ return this.map.removeIf((final K key, final V value) -> {
-+ return filter.test(key);
-+ }) != 0;
-+ }
-+
-+ @Override
-+ public String toString() {
-+ return CollectionUtil.toString(this, "SWMRHashTableKeySet");
-+ }
-+ }
-+
-+ protected static final class ValueCollection<K, V> extends ViewSet<K, V, V> implements Collection<V> {
-+
-+ protected ValueCollection(final SWMRHashTable<K, V> map) {
-+ super(map);
-+ }
-+
-+ @Override
-+ public Iterator<V> iterator() {
-+ return new ValueIterator<>(this.map.getTableAcquire(), this.map);
-+ }
-+
-+ @Override
-+ public void forEach(final Consumer<? super V> action) {
-+ Validate.notNull(action, "Null action");
-+
-+ this.map.forEachValue(action);
-+ }
-+
-+ @Override
-+ public boolean contains(final Object object) {
-+ Validate.notNull(object, "Null object");
-+
-+ return this.map.containsValue(object);
-+ }
-+
-+ @Override
-+ public boolean remove(final Object object) {
-+ Validate.notNull(object, "Null object");
-+
-+ final Iterator<V> itr = this.iterator();
-+ while (itr.hasNext()) {
-+ final V val = itr.next();
-+ if (val == object || val.equals(object)) {
-+ itr.remove();
-+ return true;
-+ }
-+ }
-+
-+ return false;
-+ }
-+
-+ @Override
-+ public boolean removeIf(final Predicate<? super V> filter) {
-+ Validate.notNull(filter, "Null filter");
-+
-+ return this.map.removeIf((final K key, final V value) -> {
-+ return filter.test(value);
-+ }) != 0;
-+ }
-+
-+ @Override
-+ public boolean retainAll(final Collection<?> collection) {
-+ Validate.notNull(collection, "Null collection");
-+
-+ return this.map.removeIf((final K key, final V value) -> {
-+ return !collection.contains(value);
-+ }) != 0;
-+ }
-+
-+ @Override
-+ public String toString() {
-+ return CollectionUtil.toString(this, "SWMRHashTableValues");
-+ }
-+ }
-+}
-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..bb301a9f4e3ac919552eef68afc73569d50674db
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/map/SWMRLong2ObjectHashTable.java
-@@ -0,0 +1,674 @@
-+package ca.spottedleaf.concurrentutil.map;
-+
-+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 java.lang.invoke.VarHandle;
-+import java.util.Arrays;
-+import java.util.function.Consumer;
-+import java.util.function.LongConsumer;
-+
-+// trimmed down version of SWMRHashTable
-+public class SWMRLong2ObjectHashTable<V> {
-+
-+ protected int size;
-+
-+ protected TableEntry<V>[] table;
-+
-+ protected final float loadFactor;
-+
-+ protected static final VarHandle SIZE_HANDLE = ConcurrentUtil.getVarHandle(SWMRLong2ObjectHashTable.class, "size", int.class);
-+ protected static final VarHandle TABLE_HANDLE = ConcurrentUtil.getVarHandle(SWMRLong2ObjectHashTable.class, "table", TableEntry[].class);
-+
-+ /* size */
-+
-+ protected final int getSizePlain() {
-+ return (int)SIZE_HANDLE.get(this);
-+ }
-+
-+ protected final int getSizeOpaque() {
-+ return (int)SIZE_HANDLE.getOpaque(this);
-+ }
-+
-+ protected final int getSizeAcquire() {
-+ return (int)SIZE_HANDLE.getAcquire(this);
-+ }
-+
-+ protected final void setSizePlain(final int value) {
-+ SIZE_HANDLE.set(this, value);
-+ }
-+
-+ protected final void setSizeOpaque(final int value) {
-+ SIZE_HANDLE.setOpaque(this, value);
-+ }
-+
-+ protected final void setSizeRelease(final int value) {
-+ SIZE_HANDLE.setRelease(this, value);
-+ }
-+
-+ /* table */
-+
-+ protected final TableEntry<V>[] getTablePlain() {
-+ //noinspection unchecked
-+ return (TableEntry<V>[])TABLE_HANDLE.get(this);
-+ }
-+
-+ protected final TableEntry<V>[] getTableAcquire() {
-+ //noinspection unchecked
-+ return (TableEntry<V>[])TABLE_HANDLE.getAcquire(this);
-+ }
-+
-+ protected final void setTablePlain(final TableEntry<V>[] table) {
-+ TABLE_HANDLE.set(this, table);
-+ }
-+
-+ protected final void setTableRelease(final TableEntry<V>[] table) {
-+ TABLE_HANDLE.setRelease(this, table);
-+ }
-+
-+ 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;
-+
-+ /**
-+ * Constructs this map with a capacity of {@code 16} and load factor of {@code 0.75f}.
-+ */
-+ public SWMRLong2ObjectHashTable() {
-+ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
-+ }
-+
-+ /**
-+ * Constructs this map with the specified capacity and load factor of {@code 0.75f}.
-+ * @param capacity specified initial capacity, > 0
-+ */
-+ public SWMRLong2ObjectHashTable(final int capacity) {
-+ this(capacity, DEFAULT_LOAD_FACTOR);
-+ }
-+
-+ /**
-+ * Constructs this map with the specified capacity and load factor.
-+ * @param capacity specified capacity, > 0
-+ * @param loadFactor specified load factor, > 0 && finite
-+ */
-+ public SWMRLong2ObjectHashTable(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);
-+ }
-+
-+ //noinspection unchecked
-+ final TableEntry<V>[] table = new TableEntry[tableSize];
-+ this.setTablePlain(table);
-+
-+ if (tableSize == MAXIMUM_CAPACITY) {
-+ this.threshold = -1;
-+ } else {
-+ this.threshold = getTargetCapacity(tableSize, loadFactor);
-+ }
-+
-+ this.loadFactor = loadFactor;
-+ }
-+
-+ /**
-+ * Constructs this map with a capacity of {@code 16} or the specified map's size, whichever is larger, and
-+ * with a load factor of {@code 0.75f}.
-+ * All of the specified map's entries are copied into this map.
-+ * @param other The specified map.
-+ */
-+ public SWMRLong2ObjectHashTable(final SWMRLong2ObjectHashTable<V> other) {
-+ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, other);
-+ }
-+
-+ /**
-+ * Constructs this map with a minimum capacity of the specified capacity or the specified map's size, whichever is larger, and
-+ * with a load factor of {@code 0.75f}.
-+ * All of the specified map's entries are copied into this map.
-+ * @param capacity specified capacity, > 0
-+ * @param other The specified map.
-+ */
-+ public SWMRLong2ObjectHashTable(final int capacity, final SWMRLong2ObjectHashTable<V> other) {
-+ this(capacity, DEFAULT_LOAD_FACTOR, other);
-+ }
-+
-+ /**
-+ * Constructs this map with a min capacity of the specified capacity or the specified map's size, whichever is larger, and
-+ * 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 && finite
-+ * @param other The specified map.
-+ */
-+ public SWMRLong2ObjectHashTable(final int capacity, final float loadFactor, final SWMRLong2ObjectHashTable<V> other) {
-+ this(Math.max(Validate.notNull(other, "Null map").size(), capacity), loadFactor);
-+ 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;
-+ }
-+
-+ 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);
-+ }
-+
-+ /** Callers must still use acquire when reading the value of the entry. */
-+ protected final TableEntry<V> getEntryForOpaque(final long key) {
-+ final int hash = SWMRLong2ObjectHashTable.getHash(key);
-+ final TableEntry<V>[] table = this.getTableAcquire();
-+
-+ for (TableEntry<V> curr = getAtIndexOpaque(table, hash & (table.length - 1)); curr != null; curr = curr.getNextOpaque()) {
-+ if (key == curr.key) {
-+ return curr;
-+ }
-+ }
-+
-+ return null;
-+ }
-+
-+ protected final TableEntry<V> getEntryForPlain(final long key) {
-+ final int hash = SWMRLong2ObjectHashTable.getHash(key);
-+ final TableEntry<V>[] table = this.getTablePlain();
-+
-+ for (TableEntry<V> curr = table[hash & (table.length - 1)]; curr != null; curr = curr.getNextPlain()) {
-+ if (key == curr.key) {
-+ return curr;
-+ }
-+ }
-+
-+ return null;
-+ }
-+
-+ /* MT-Safe */
-+
-+ /** must be deterministic given a key */
-+ protected static int getHash(final long key) {
-+ return (int)HashUtil.mix(key);
-+ }
-+
-+ // rets -1 if capacity*loadFactor is too large
-+ protected static int getTargetCapacity(final int capacity, final float loadFactor) {
-+ final double ret = (double)capacity * (double)loadFactor;
-+ if (Double.isInfinite(ret) || ret >= ((double)Integer.MAX_VALUE)) {
-+ return -1;
-+ }
-+
-+ return (int)ret;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public boolean equals(final Object obj) {
-+ if (this == obj) {
-+ return true;
-+ }
-+ /* Make no attempt to deal with concurrent modifications */
-+ if (!(obj instanceof SWMRLong2ObjectHashTable<?> other)) {
-+ return false;
-+ }
-+
-+ if (this.size() != other.size()) {
-+ return false;
-+ }
-+
-+ final TableEntry<V>[] table = this.getTableAcquire();
-+
-+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<V> curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
-+ final V value = curr.getValueAcquire();
-+
-+ final Object otherValue = other.get(curr.key);
-+ if (otherValue == null || (value != otherValue && value.equals(otherValue))) {
-+ return false;
-+ }
-+ }
-+ }
-+
-+ return true;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public int hashCode() {
-+ /* Make no attempt to deal with concurrent modifications */
-+ int hash = 0;
-+ final TableEntry<V>[] table = this.getTableAcquire();
-+
-+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<V> curr = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
-+ hash += curr.hashCode();
-+ }
-+ }
-+
-+ return hash;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public String toString() {
-+ final StringBuilder builder = new StringBuilder(64);
-+ builder.append("SingleWriterMultiReaderHashMap:{");
-+
-+ this.forEach((final long key, final V value) -> {
-+ builder.append("{key: \"").append(key).append("\", value: \"").append(value).append("\"}");
-+ });
-+
-+ return builder.append('}').toString();
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ @Override
-+ public SWMRLong2ObjectHashTable<V> clone() {
-+ return new SWMRLong2ObjectHashTable<>(this.getTableAcquire().length, this.loadFactor, this);
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ 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 = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
-+ action.accept(curr);
-+ }
-+ }
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ public void forEach(final BiLongObjectConsumer<? super 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 = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
-+ final V value = curr.getValueAcquire();
-+
-+ action.accept(curr.key, value);
-+ }
-+ }
-+ }
-+
-+ /**
-+ * Provides the specified consumer with all keys contained within this map.
-+ * @param action The specified consumer.
-+ */
-+ public void forEachKey(final LongConsumer 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 = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
-+ action.accept(curr.key);
-+ }
-+ }
-+ }
-+
-+ /**
-+ * Provides the specified consumer with all values contained within this map. Equivalent to {@code map.values().forEach(Consumer)}.
-+ * @param action The specified consumer.
-+ */
-+ public void forEachValue(final Consumer<? super 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 = getAtIndexOpaque(table, i); curr != null; curr = curr.getNextOpaque()) {
-+ final V value = curr.getValueAcquire();
-+
-+ action.accept(value);
-+ }
-+ }
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ public V get(final long key) {
-+ final TableEntry<V> entry = this.getEntryForOpaque(key);
-+ return entry == null ? null : entry.getValueAcquire();
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ public boolean containsKey(final long key) {
-+ // note: we need to use getValueAcquire, so that the reads from this map are ordered by acquire semantics
-+ return this.get(key) != null;
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ public V getOrDefault(final long key, final V defaultValue) {
-+ final TableEntry<V> entry = this.getEntryForOpaque(key);
-+
-+ return entry == null ? defaultValue : entry.getValueAcquire();
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ public int size() {
-+ return this.getSizeAcquire();
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ public boolean isEmpty() {
-+ return this.getSizeAcquire() == 0;
-+ }
-+
-+ /* Non-MT-Safe */
-+
-+ protected int threshold;
-+
-+ protected final void checkResize(final int minCapacity) {
-+ if (minCapacity <= this.threshold || this.threshold < 0) {
-+ return;
-+ }
-+
-+ final TableEntry<V>[] table = this.getTablePlain();
-+ int newCapacity = minCapacity >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : IntegerUtil.roundCeilLog2(minCapacity);
-+ if (newCapacity < 0) {
-+ newCapacity = MAXIMUM_CAPACITY;
-+ }
-+ if (newCapacity <= table.length) {
-+ if (newCapacity == MAXIMUM_CAPACITY) {
-+ return;
-+ }
-+ newCapacity = table.length << 1;
-+ }
-+
-+ //noinspection unchecked
-+ final TableEntry<V>[] newTable = new TableEntry[newCapacity];
-+ final int indexMask = newCapacity - 1;
-+
-+ for (int i = 0, len = table.length; i < len; ++i) {
-+ for (TableEntry<V> entry = table[i]; entry != null; entry = entry.getNextPlain()) {
-+ final long key = entry.key;
-+ final int hash = SWMRLong2ObjectHashTable.getHash(key);
-+ final int index = hash & indexMask;
-+
-+ /* we need to create a new entry since there could be reading threads */
-+ final TableEntry<V> insert = new TableEntry<>(key, entry.getValuePlain());
-+
-+ final TableEntry<V> prev = newTable[index];
-+
-+ newTable[index] = insert;
-+ insert.setNextPlain(prev);
-+ }
-+ }
-+
-+ if (newCapacity == MAXIMUM_CAPACITY) {
-+ this.threshold = -1; /* No more resizing */
-+ } else {
-+ this.threshold = getTargetCapacity(newCapacity, this.loadFactor);
-+ }
-+ this.setTableRelease(newTable); /* use release to publish entries in table */
-+ }
-+
-+ protected final int addToSize(final int num) {
-+ final int newSize = this.getSizePlain() + num;
-+
-+ this.setSizeOpaque(newSize);
-+ this.checkResize(newSize);
-+
-+ return newSize;
-+ }
-+
-+ protected final int removeFromSize(final int num) {
-+ final int newSize = this.getSizePlain() - num;
-+
-+ this.setSizeOpaque(newSize);
-+
-+ return newSize;
-+ }
-+
-+ protected final V put(final long key, final V value, final boolean onlyIfAbsent) {
-+ final TableEntry<V>[] table = this.getTablePlain();
-+ final int hash = SWMRLong2ObjectHashTable.getHash(key);
-+ final int index = hash & (table.length - 1);
-+
-+ final TableEntry<V> head = table[index];
-+ if (head == null) {
-+ final TableEntry<V> insert = new TableEntry<>(key, value);
-+ setAtIndexRelease(table, index, insert);
-+ this.addToSize(1);
-+ return null;
-+ }
-+
-+ for (TableEntry<V> curr = head;;) {
-+ if (key == curr.key) {
-+ if (onlyIfAbsent) {
-+ return curr.getValuePlain();
-+ }
-+
-+ final V currVal = curr.getValuePlain();
-+ curr.setValueRelease(value);
-+ return currVal;
-+ }
-+
-+ final TableEntry<V> next = curr.getNextPlain();
-+ if (next != null) {
-+ curr = next;
-+ continue;
-+ }
-+
-+ final TableEntry<V> insert = new TableEntry<>(key, value);
-+
-+ curr.setNextRelease(insert);
-+ this.addToSize(1);
-+ return null;
-+ }
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ public V put(final long key, final V value) {
-+ Validate.notNull(value, "Null value");
-+
-+ return this.put(key, value, false);
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ */
-+ public V putIfAbsent(final long key, final V value) {
-+ Validate.notNull(value, "Null value");
-+
-+ return this.put(key, value, true);
-+ }
-+
-+ protected final V remove(final long key, final int hash) {
-+ 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) {
-+ setAtIndexRelease(table, index, head.getNextPlain());
-+ this.removeFromSize(1);
-+
-+ return head.getValuePlain();
-+ }
-+
-+ for (TableEntry<V> curr = head.getNextPlain(), prev = head; curr != null; prev = curr, curr = curr.getNextPlain()) {
-+ if (key == curr.key) {
-+ prev.setNextRelease(curr.getNextPlain());
-+ this.removeFromSize(1);
-+
-+ return curr.getValuePlain();
-+ }
-+ }
-+
-+ 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}
-+ */
-+ public V remove(final long key) {
-+ 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}
-+ */
-+ public void putAll(final SWMRLong2ObjectHashTable<? extends V> map) {
-+ Validate.notNull(map, "Null map");
-+
-+ final int size = map.size();
-+ this.checkResize(Math.max(this.getSizePlain() + size/2, size)); /* preemptively resize */
-+ map.forEach(this::put);
-+ }
-+
-+ /**
-+ * {@inheritDoc}
-+ * <p>
-+ * This call is non-atomic and the order that which entries are removed is undefined. The clear operation itself
-+ * is release ordered, that is, after the clear operation is performed a release fence is performed.
-+ * </p>
-+ */
-+ public void clear() {
-+ Arrays.fill(this.getTablePlain(), null);
-+ this.setSizeRelease(0);
-+ }
-+
-+ 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;
-+
-+ protected TableEntry<V> next;
-+
-+ protected static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class);
-+ protected static final VarHandle NEXT_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "next", TableEntry.class);
-+
-+ /* value */
-+
-+ 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 void setValueRelease(final V to) {
-+ VALUE_HANDLE.setRelease(this, to);
-+ }
-+
-+ /* next */
-+
-+ protected final TableEntry<V> getNextPlain() {
-+ //noinspection unchecked
-+ return (TableEntry<V>)NEXT_HANDLE.get(this);
-+ }
-+
-+ protected final TableEntry<V> getNextOpaque() {
-+ //noinspection unchecked
-+ return (TableEntry<V>)NEXT_HANDLE.getOpaque(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 TableEntry(final long key, final V value) {
-+ this.key = key;
-+ this.value = value;
-+ }
-+
-+ public long getKey() {
-+ return this.key;
-+ }
-+
-+ 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..85e6ef636d435a0ee4bf3e0760b0c87422c520a1
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/scheduler/SchedulerThreadPool.java
-@@ -0,0 +1,564 @@
-+package ca.spottedleaf.concurrentutil.scheduler;
-+
-+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;
-+
-+/**
-+ * @deprecated To be replaced
-+ */
-+@Deprecated
-+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;
-+ }
-+ }
-+ }
-+
-+ return true;
-+ }
-+
-+ /**
-+ * 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>
-+ * @deprecated To be replaced
-+ */
-+ @Deprecated
-+ public static abstract class SchedulableTick {
-+ private static final AtomicLong ID_GENERATOR = new AtomicLong();
-+ public final long id = ID_GENERATOR.getAndIncrement();
-+
-+ 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);
-+ }
-+
-+ 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;
-+ }
-+
-+ /**
-+ * 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 String toString() {
-+ return "SchedulableTick:{" +
-+ "class=" + this.getClass().getName() + "," +
-+ "scheduled_state=" + this.scheduled.get() + ","
-+ + "}";
-+ }
-+ }
-+
-+ private static final class TickThreadRunner implements Runnable {
-+
-+ /**
-+ * 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 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..82c4c11b0b564c97ac92bd5f54e3754a7ba95184
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedSortedSet.java
-@@ -0,0 +1,270 @@
-+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;
-+
-+ private Link<E> head;
-+ private 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;
-+ }
-+
-+ 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;
-+ }
-+ }
-+ 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(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;
-+ }
-+ }
-+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..bd8eb4f25d1dee00fbf9c05c14b0d94c5c641a55
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/set/LinkedUnsortedList.java
-@@ -0,0 +1,204 @@
-+package ca.spottedleaf.concurrentutil.set;
-+
-+import java.util.Iterator;
-+import java.util.NoSuchElementException;
-+import java.util.Objects;
-+
-+public final class LinkedUnsortedList<E> implements Iterable<E> {
-+
-+ private Link<E> head;
-+ private Link<E> tail;
-+
-+ public LinkedUnsortedList() {}
-+
-+ 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) {
-+ for (Link<E> curr = this.head; curr != null; curr = curr.next) {
-+ if (Objects.equals(element, curr.element)) {
-+ return true;
-+ }
-+ }
-+ return false;
-+ }
-+
-+ public boolean containsLast(final E element) {
-+ for (Link<E> curr = this.tail; curr != null; curr = curr.prev) {
-+ if (Objects.equals(element, curr.element)) {
-+ return true;
-+ }
-+ }
-+ 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) {
-+ for (Link<E> curr = this.head; curr != null; curr = curr.next) {
-+ if (Objects.equals(element, curr.element)) {
-+ this.removeNode(curr);
-+ return true;
-+ }
-+ }
-+ return false;
-+ }
-+
-+ public boolean removeLast(final E element) {
-+ for (Link<E> curr = this.tail; curr != null; curr = curr.prev) {
-+ if (Objects.equals(element, curr.element)) {
-+ this.removeNode(curr);
-+ return true;
-+ }
-+ }
-+ return false;
-+ }
-+
-+ @Override
-+ public Iterator<E> iterator() {
-+ return new Iterator<>() {
-+ private Link<E> next = LinkedUnsortedList.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 Link<E> curr = this.tail;
-+ if (curr != null) {
-+ return this.tail = new Link<>(element, curr, null);
-+ } else {
-+ return this.head = this.tail = new Link<>(element);
-+ }
-+ }
-+
-+ public Link<E> addFirst(final E element) {
-+ final Link<E> curr = this.head;
-+ if (curr != null) {
-+ return this.head = new Link<>(element, null, curr);
-+ } else {
-+ return this.head = this.tail = new Link<>(element);
-+ }
-+ }
-+
-+ public static final class Link<E> {
-+ private E element;
-+ private Link<E> prev;
-+ private Link<E> next;
-+
-+ 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;
-+ }
-+ }
-+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/CollectionUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/CollectionUtil.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..9420b9822de99d3a31224642452835b0c986f7b4
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/util/CollectionUtil.java
-@@ -0,0 +1,31 @@
-+package ca.spottedleaf.concurrentutil.util;
-+
-+import java.util.Collection;
-+
-+public final class CollectionUtil {
-+
-+ public static String toString(final Collection<?> collection, final String name) {
-+ return CollectionUtil.toString(collection, name, new StringBuilder(name.length() + 128)).toString();
-+ }
-+
-+ public static StringBuilder toString(final Collection<?> collection, final String name, final StringBuilder builder) {
-+ builder.append(name).append("{elements={");
-+
-+ boolean first = true;
-+
-+ for (final Object element : collection) {
-+ if (!first) {
-+ builder.append(", ");
-+ }
-+ first = false;
-+
-+ builder.append('"').append(element).append('"');
-+ }
-+
-+ return builder.append("}}");
-+ }
-+
-+ private CollectionUtil() {
-+ throw new RuntimeException();
-+ }
-+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/ConcurrentUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/ConcurrentUtil.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..23ae82e55696a7e2ff0e0f9609c0df6a48bb8d1d
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/util/ConcurrentUtil.java
-@@ -0,0 +1,166 @@
-+package ca.spottedleaf.concurrentutil.util;
-+
-+import java.lang.invoke.MethodHandles;
-+import java.lang.invoke.VarHandle;
-+import java.util.concurrent.locks.LockSupport;
-+
-+public final class ConcurrentUtil {
-+
-+ public static String genericToString(final Object object) {
-+ return object == null ? "null" : object.getClass().getName() + ":" + object.hashCode() + ":" + object.toString();
-+ }
-+
-+ public static void rethrow(Throwable exception) {
-+ rethrow0(exception);
-+ }
-+
-+ private static <T extends Throwable> void rethrow0(Throwable thr) throws T {
-+ throw (T)thr;
-+ }
-+
-+ public static VarHandle getVarHandle(final Class<?> lookIn, final String fieldName, final Class<?> fieldType) {
-+ try {
-+ return MethodHandles.privateLookupIn(lookIn, MethodHandles.lookup()).findVarHandle(lookIn, fieldName, fieldType);
-+ } catch (final Exception ex) {
-+ throw new RuntimeException(ex); // unreachable
-+ }
-+ }
-+
-+ public static VarHandle getStaticVarHandle(final Class<?> lookIn, final String fieldName, final Class<?> fieldType) {
-+ try {
-+ return MethodHandles.privateLookupIn(lookIn, MethodHandles.lookup()).findStaticVarHandle(lookIn, fieldName, fieldType);
-+ } catch (final Exception ex) {
-+ throw new RuntimeException(ex); // unreachable
-+ }
-+ }
-+
-+ /**
-+ * Non-exponential backoff algorithm to use in lightly contended areas.
-+ * @see ConcurrentUtil#exponentiallyBackoffSimple(long)
-+ * @see ConcurrentUtil#exponentiallyBackoffComplex(long)
-+ */
-+ public static void backoff() {
-+ Thread.onSpinWait();
-+ }
-+
-+ /**
-+ * Backoff algorithm to use for a short held lock (i.e compareAndExchange operation). Generally this should not be
-+ * used when a thread can block another thread. Instead, use {@link ConcurrentUtil#exponentiallyBackoffComplex(long)}.
-+ * @param counter The current counter.
-+ * @return The counter plus 1.
-+ * @see ConcurrentUtil#backoff()
-+ * @see ConcurrentUtil#exponentiallyBackoffComplex(long)
-+ */
-+ public static long exponentiallyBackoffSimple(final long counter) {
-+ for (long i = 0; i < counter; ++i) {
-+ backoff();
-+ }
-+ return counter + 1L;
-+ }
-+
-+ /**
-+ * Backoff algorithm to use for a lock that can block other threads (i.e if another thread contending with this thread
-+ * can be thrown off the scheduler). This lock should not be used for simple locks such as compareAndExchange.
-+ * @param counter The current counter.
-+ * @return The next (if any) step in the backoff logic.
-+ * @see ConcurrentUtil#backoff()
-+ * @see ConcurrentUtil#exponentiallyBackoffSimple(long)
-+ */
-+ public static long exponentiallyBackoffComplex(final long counter) {
-+ // TODO experimentally determine counters
-+ if (counter < 100L) {
-+ return exponentiallyBackoffSimple(counter);
-+ }
-+ if (counter < 1_200L) {
-+ Thread.yield();
-+ LockSupport.parkNanos(1_000L);
-+ return counter + 1L;
-+ }
-+ // scale 0.1ms (100us) per failure
-+ Thread.yield();
-+ LockSupport.parkNanos(100_000L * counter);
-+ return counter + 1;
-+ }
-+
-+ /**
-+ * Simple exponential backoff that will linearly increase the time per failure, according to the scale.
-+ * @param counter The current failure counter.
-+ * @param scale Time per failure, in ns.
-+ * @param max The maximum time to wait for, in ns.
-+ * @return The next counter.
-+ */
-+ public static long linearLongBackoff(long counter, final long scale, long max) {
-+ counter = Math.min(Long.MAX_VALUE, counter + 1); // prevent overflow
-+ max = Math.max(0, max);
-+
-+ if (scale <= 0L) {
-+ return counter;
-+ }
-+
-+ long time = scale * counter;
-+
-+ if (time > max || time / scale != counter) {
-+ time = max;
-+ }
-+
-+ boolean interrupted = Thread.interrupted();
-+ if (time > 1_000_000L) { // 1ms
-+ Thread.yield();
-+ }
-+ LockSupport.parkNanos(time);
-+ if (interrupted) {
-+ Thread.currentThread().interrupt();
-+ }
-+ return counter;
-+ }
-+
-+ /**
-+ * Simple exponential backoff that will linearly increase the time per failure, according to the scale.
-+ * @param counter The current failure counter.
-+ * @param scale Time per failure, in ns.
-+ * @param max The maximum time to wait for, in ns.
-+ * @param deadline The deadline in ns. Deadline time source: {@link System#nanoTime()}.
-+ * @return The next counter.
-+ */
-+ public static long linearLongBackoffDeadline(long counter, final long scale, long max, long deadline) {
-+ counter = Math.min(Long.MAX_VALUE, counter + 1); // prevent overflow
-+ max = Math.max(0, max);
-+
-+ if (scale <= 0L) {
-+ return counter;
-+ }
-+
-+ long time = scale * counter;
-+
-+ // check overflow
-+ if (time / scale != counter) {
-+ // overflew
-+ --counter;
-+ time = max;
-+ } else if (time > max) {
-+ time = max;
-+ }
-+
-+ final long currTime = System.nanoTime();
-+ final long diff = deadline - currTime;
-+ if (diff <= 0) {
-+ return counter;
-+ }
-+ if (diff <= 1_500_000L) { // 1.5ms
-+ time = 100_000L; // 100us
-+ } else if (time > 1_000_000L) { // 1ms
-+ Thread.yield();
-+ }
-+
-+ boolean interrupted = Thread.interrupted();
-+ LockSupport.parkNanos(time);
-+ if (interrupted) {
-+ Thread.currentThread().interrupt();
-+ }
-+ return counter;
-+ }
-+
-+ public static VarHandle getArrayHandle(final Class<?> type) {
-+ return MethodHandles.arrayElementVarHandle(type);
-+ }
-+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/HashUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/HashUtil.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..2b9f36211d1cbb4fcf1457c0a83592499e9aa23b
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/util/HashUtil.java
-@@ -0,0 +1,111 @@
-+package ca.spottedleaf.concurrentutil.util;
-+
-+public final class HashUtil {
-+
-+ // Copied from fastutil HashCommon
-+
-+ /** 2<sup>32</sup> &middot; &phi;, &phi; = (&#x221A;5 &minus; 1)/2. */
-+ private static final int INT_PHI = 0x9E3779B9;
-+ /** The reciprocal of {@link #INT_PHI} modulo 2<sup>32</sup>. */
-+ private static final int INV_INT_PHI = 0x144cbc89;
-+ /** 2<sup>64</sup> &middot; &phi;, &phi; = (&#x221A;5 &minus; 1)/2. */
-+ private static final long LONG_PHI = 0x9E3779B97F4A7C15L;
-+ /** The reciprocal of {@link #LONG_PHI} modulo 2<sup>64</sup>. */
-+ private static final long INV_LONG_PHI = 0xf1de83e19937733dL;
-+
-+ /** Avalanches the bits of an integer by applying the finalisation step of MurmurHash3.
-+ *
-+ * <p>This method implements the finalisation step of Austin Appleby's <a href="http://code.google.com/p/smhasher/">MurmurHash3</a>.
-+ * Its purpose is to avalanche the bits of the argument to within 0.25% bias.
-+ *
-+ * @param x an integer.
-+ * @return a hash value with good avalanching properties.
-+ */
-+ // additional note: this function is a bijection onto all integers
-+ public static int murmurHash3(int x) {
-+ x ^= x >>> 16;
-+ x *= 0x85ebca6b;
-+ x ^= x >>> 13;
-+ x *= 0xc2b2ae35;
-+ x ^= x >>> 16;
-+ return x;
-+ }
-+
-+
-+ /** Avalanches the bits of a long integer by applying the finalisation step of MurmurHash3.
-+ *
-+ * <p>This method implements the finalisation step of Austin Appleby's <a href="http://code.google.com/p/smhasher/">MurmurHash3</a>.
-+ * Its purpose is to avalanche the bits of the argument to within 0.25% bias.
-+ *
-+ * @param x a long integer.
-+ * @return a hash value with good avalanching properties.
-+ */
-+ // additional note: this function is a bijection onto all longs
-+ public static long murmurHash3(long x) {
-+ x ^= x >>> 33;
-+ x *= 0xff51afd7ed558ccdL;
-+ x ^= x >>> 33;
-+ x *= 0xc4ceb9fe1a85ec53L;
-+ x ^= x >>> 33;
-+ return x;
-+ }
-+
-+ /** Quickly mixes the bits of an integer.
-+ *
-+ * <p>This method mixes the bits of the argument by multiplying by the golden ratio and
-+ * xorshifting the result. It is borrowed from <a href="https://github.com/leventov/Koloboke">Koloboke</a>, and
-+ * it has slightly worse behaviour than {@link #murmurHash3(int)} (in open-addressing hash tables the average number of probes
-+ * is slightly larger), but it's much faster.
-+ *
-+ * @param x an integer.
-+ * @return a hash value obtained by mixing the bits of {@code x}.
-+ * @see #invMix(int)
-+ */
-+ // additional note: this function is a bijection onto all integers
-+ public static int mix(final int x) {
-+ final int h = x * INT_PHI;
-+ return h ^ (h >>> 16);
-+ }
-+
-+ /** The inverse of {@link #mix(int)}. This method is mainly useful to create unit tests.
-+ *
-+ * @param x an integer.
-+ * @return a value that passed through {@link #mix(int)} would give {@code x}.
-+ */
-+ // additional note: this function is a bijection onto all integers
-+ public static int invMix(final int x) {
-+ return (x ^ x >>> 16) * INV_INT_PHI;
-+ }
-+
-+ /** Quickly mixes the bits of a long integer.
-+ *
-+ * <p>This method mixes the bits of the argument by multiplying by the golden ratio and
-+ * xorshifting twice the result. It is borrowed from <a href="https://github.com/leventov/Koloboke">Koloboke</a>, and
-+ * it has slightly worse behaviour than {@link #murmurHash3(long)} (in open-addressing hash tables the average number of probes
-+ * is slightly larger), but it's much faster.
-+ *
-+ * @param x a long integer.
-+ * @return a hash value obtained by mixing the bits of {@code x}.
-+ */
-+ // additional note: this function is a bijection onto all longs
-+ public static long mix(final long x) {
-+ long h = x * LONG_PHI;
-+ h ^= h >>> 32;
-+ return h ^ (h >>> 16);
-+ }
-+
-+ /** The inverse of {@link #mix(long)}. This method is mainly useful to create unit tests.
-+ *
-+ * @param x a long integer.
-+ * @return a value that passed through {@link #mix(long)} would give {@code x}.
-+ */
-+ // additional note: this function is a bijection onto all longs
-+ public static long invMix(long x) {
-+ x ^= x >>> 32;
-+ x ^= x >>> 16;
-+ return (x ^ x >>> 32) * INV_LONG_PHI;
-+ }
-+
-+
-+ private HashUtil() {}
-+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/IntPairUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/IntPairUtil.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..4e61c477a56e645228d5a2015c26816954d17bf8
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/util/IntPairUtil.java
-@@ -0,0 +1,46 @@
-+package ca.spottedleaf.concurrentutil.util;
-+
-+public final class IntPairUtil {
-+
-+ /**
-+ * Packs the specified integers into one long value.
-+ */
-+ public static long key(final int left, final int right) {
-+ return ((long)right << 32) | (left & 0xFFFFFFFFL);
-+ }
-+
-+ /**
-+ * Retrieves the left packed integer from the key
-+ */
-+ public static int left(final long key) {
-+ return (int)key;
-+ }
-+
-+ /**
-+ * Retrieves the right packed integer from the key
-+ */
-+ public static int right(final long key) {
-+ return (int)(key >>> 32);
-+ }
-+
-+ public static String toString(final long key) {
-+ return "{left:" + left(key) + ", right:" + right(key) + "}";
-+ }
-+
-+ public static String toString(final long[] array, final int from, final int to) {
-+ final StringBuilder ret = new StringBuilder();
-+ ret.append("[");
-+
-+ for (int i = from; i < to; ++i) {
-+ if (i != from) {
-+ ret.append(", ");
-+ }
-+ ret.append(toString(array[i]));
-+ }
-+
-+ ret.append("]");
-+ return ret.toString();
-+ }
-+
-+ private IntPairUtil() {}
-+}
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..9d7b9b8158cd01d12adbd7896ff77bee9828e101
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java
-@@ -0,0 +1,196 @@
-+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
-+ }
-+
-+ // https://lemire.me/blog/2019/02/08/faster-remainders-when-the-divisor-is-a-constant-beating-compilers-and-libdivide
-+ /**
-+ *
-+ * Usage:
-+ * <pre>
-+ * {@code
-+ * static final long mult = getSimpleMultiplier(divisor, bits);
-+ * long x = ...;
-+ * long magic = x * mult;
-+ * long divQ = magic >>> bits;
-+ * long divR = ((magic & ((1 << bits) - 1)) * divisor) >>> bits;
-+ * }
-+ * </pre>
-+ *
-+ * @param bits The number of bits of precision for the returned result
-+ */
-+ public static long getUnsignedDivisorMagic(final long divisor, final int bits) {
-+ return (((1L << bits) - 1L) / divisor) + 1;
-+ }
-+
-+ private IntegerUtil() {
-+ throw new RuntimeException();
-+ }
-+}
-\ No newline at end of file
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java b/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..2919bbaa07b70f182438c3be8f9ebbe0649809b6
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/util/Priority.java
-@@ -0,0 +1,145 @@
-+package ca.spottedleaf.concurrentutil.util;
-+
-+public enum Priority {
-+
-+ /**
-+ * Priority value indicating the task has completed or is being completed.
-+ * This priority cannot be used to schedule tasks.
-+ */
-+ COMPLETING(-1),
-+
-+ /**
-+ * Absolute highest priority, should only be used for when a task is blocking a time-critical thread.
-+ */
-+ BLOCKING(),
-+
-+ /**
-+ * Should only be used for urgent but not time-critical tasks.
-+ */
-+ HIGHEST(),
-+
-+ /**
-+ * Two priorities above normal.
-+ */
-+ HIGHER(),
-+
-+ /**
-+ * One priority above normal.
-+ */
-+ HIGH(),
-+
-+ /**
-+ * Default priority.
-+ */
-+ NORMAL(),
-+
-+ /**
-+ * One priority below normal.
-+ */
-+ LOW(),
-+
-+ /**
-+ * Two priorities below normal.
-+ */
-+ LOWER(),
-+
-+ /**
-+ * Use for tasks that should eventually execute, but are not needed to.
-+ */
-+ LOWEST(),
-+
-+ /**
-+ * Use for tasks that can be delayed indefinitely.
-+ */
-+ IDLE();
-+
-+ // returns whether the priority can be scheduled
-+ public static boolean isValidPriority(final Priority priority) {
-+ return priority != null && priority != priority.COMPLETING;
-+ }
-+
-+ // returns the higher priority of the two
-+ public static Priority max(final Priority p1, final Priority p2) {
-+ return p1.isHigherOrEqualPriority(p2) ? p1 : p2;
-+ }
-+
-+ // returns the lower priroity of the two
-+ public static Priority min(final Priority p1, final Priority p2) {
-+ return p1.isLowerOrEqualPriority(p2) ? p1 : p2;
-+ }
-+
-+ public boolean isHigherOrEqualPriority(final Priority than) {
-+ return this.priority <= than.priority;
-+ }
-+
-+ public boolean isHigherPriority(final Priority than) {
-+ return this.priority < than.priority;
-+ }
-+
-+ public boolean isLowerOrEqualPriority(final Priority than) {
-+ return this.priority >= than.priority;
-+ }
-+
-+ public boolean isLowerPriority(final Priority than) {
-+ return this.priority > than.priority;
-+ }
-+
-+ public boolean isHigherOrEqualPriority(final int than) {
-+ return this.priority <= than;
-+ }
-+
-+ public boolean isHigherPriority(final int than) {
-+ return this.priority < than;
-+ }
-+
-+ public boolean isLowerOrEqualPriority(final int than) {
-+ return this.priority >= than;
-+ }
-+
-+ public boolean isLowerPriority(final int than) {
-+ return this.priority > than;
-+ }
-+
-+ public static boolean isHigherOrEqualPriority(final int priority, final int than) {
-+ return priority <= than;
-+ }
-+
-+ public static boolean isHigherPriority(final int priority, final int than) {
-+ return priority < than;
-+ }
-+
-+ public static boolean isLowerOrEqualPriority(final int priority, final int than) {
-+ return priority >= than;
-+ }
-+
-+ public static boolean isLowerPriority(final int priority, final int than) {
-+ return priority > than;
-+ }
-+
-+ static final Priority[] PRIORITIES = Priority.values();
-+
-+ /** includes special priorities */
-+ public static final int TOTAL_PRIORITIES = PRIORITIES.length;
-+
-+ public static final int TOTAL_SCHEDULABLE_PRIORITIES = TOTAL_PRIORITIES - 1;
-+
-+ public static Priority getPriority(final int priority) {
-+ return PRIORITIES[priority + 1];
-+ }
-+
-+ private static int priorityCounter;
-+
-+ private static int nextCounter() {
-+ return priorityCounter++;
-+ }
-+
-+ public final int priority;
-+
-+ private Priority() {
-+ this(nextCounter());
-+ }
-+
-+ private Priority(final int priority) {
-+ this.priority = priority;
-+ }
-+}
-\ No newline at end of file
-diff --git a/src/main/java/ca/spottedleaf/concurrentutil/util/ThrowUtil.java b/src/main/java/ca/spottedleaf/concurrentutil/util/ThrowUtil.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..a3a8b5c6795c4d116e094e4c910553416f565b93
---- /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
---- /dev/null
-+++ b/src/main/java/ca/spottedleaf/concurrentutil/util/Validate.java
-@@ -0,0 +1,28 @@
-+package ca.spottedleaf.concurrentutil.util;
-+
-+public final class Validate {
-+
-+ public static <T> T notNull(final T obj) {
-+ if (obj == null) {
-+ throw new NullPointerException();
-+ }
-+ return obj;
-+ }
-+
-+ public static <T> T notNull(final T obj, final String msgIfNull) {
-+ if (obj == null) {
-+ throw new NullPointerException(msgIfNull);
-+ }
-+ return obj;
-+ }
-+
-+ public static void arrayBounds(final int off, final int len, final int arrayLength, final String msgPrefix) {
-+ if (off < 0 || len < 0 || (arrayLength - off) < len) {
-+ throw new ArrayIndexOutOfBoundsException(msgPrefix + ": off: " + off + ", len: " + len + ", array length: " + arrayLength);
-+ }
-+ }
-+
-+ private Validate() {
-+ throw new RuntimeException();
-+ }
-+}
+diff --git a/build.gradle.kts b/build.gradle.kts
+index b4a389d0ef9df8ef49abb7049037e391d491d0c9..4a666be25894eae32a002a9eaf0eee22ae19da95 100644
+--- a/build.gradle.kts
++++ b/build.gradle.kts
+@@ -19,6 +19,7 @@ abstract class MockitoAgentProvider : CommandLineArgumentProvider {
+
+ dependencies {
+ implementation(project(":paper-api"))
++ implementation("ca.spottedleaf:concurrentutil:0.0.2") // Paper - Add ConcurrentUtil dependency
+ implementation("jline:jline:2.12.1")
+ implementation("org.apache.logging.log4j:log4j-iostreams:2.24.1") // Paper - remove exclusion
+ implementation("org.ow2.asm:asm-commons:9.7.1")