Paper/patches/server/0007-ConcurrentUtil.patch

10548 lines
365 KiB
Diff
Raw Normal View History

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
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;
+ }
+ }
+}
2024-10-25 21:24:15 +02:00
diff --git a/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java b/src/main/java/ca/spottedleaf/concurrentutil/completable/CallbackCompletable.java
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
new file mode 100644
2024-10-25 21:24:15 +02:00
index 0000000000000000000000000000000000000000..6bad6f8ecc0944d2f406924c7de7e227ff1e70fa
Rewrite chunk system (#8177) Patch documentation to come Issues with the old system that are fixed now: - World generation does not scale with cpu cores effectively. - Relies on the main thread for scheduling and maintaining chunk state, dropping chunk load/generate rates at lower tps. - Unreliable prioritisation of chunk gen/load calls that block the main thread. - Shutdown logic is utterly unreliable, as it has to wait for all chunks to unload - is it guaranteed that the chunk system is in a state on shutdown that it can reliably do this? Watchdog shutdown also typically failed due to thread checks, which is now resolved. - Saving of data is not unified (i.e can save chunk data without saving entity data, poses problems for desync if shutdown is really abnormal. - Entities are not loaded with chunks. This caused quite a bit of headache for Chunk#getEntities API, but now the new chunk system loads entities with chunks so that they are ready whenever the chunk loads in. Effectively brings the behavior back to 1.16 era, but still storing entities in their own separate regionfiles. The above list is not complete. The patch documentation will complete it. New chunk system hard relies on starlight and dataconverter, and most importantly the new concurrent utilities in ConcurrentUtil. Some of the old async chunk i/o interface (i.e the old file io thread reroutes _some_ calls to the new file io thread) is kept for plugin compat reasons. It will be removed in the next major version of minecraft. The old legacy chunk system patches have been moved to the removed folder in case we need them again.
2022-09-26 10:02:51 +02:00
--- /dev/null
2024-10-25 21:24:15 +02:00
+++ 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;
+
2024-10-25 21:24:15 +02:00
+public final class CallbackCompletable<T> {
+
2024-10-25 21:24:15 +02:00
+ 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() {
2024-10-25 21:24:15 +02:00
+ return CallbackCompletable.this.waiters.remove(this.waiter);
+ }
+ }
+}
2024-10-25 21:24:15 +02:00
\ 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
2024-10-25 21:24:15 +02:00
index 0000000000000000000000000000000000000000..365616439fa079017d648ed7f6ddf6950a691adf
--- /dev/null
2024-10-25 21:24:15 +02:00
+++ 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;
2024-10-25 21:24:15 +02:00
+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;
+
2024-10-25 21:24:15 +02:00
+public final class Completable<T> {
+
2024-10-25 21:24:15 +02:00
+ 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;
+ };
+
2024-10-25 21:24:15 +02:00
+ public static Executor getDefaultExecutor() {
+ return ForkJoinPool.commonPool();
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+
2024-10-25 21:24:15 +02:00
+ 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);
+
2024-10-25 21:24:15 +02:00
+ private Object getResultPlain() {
+ return (Object)RESULT_HANDLE.get(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ private Object getResultVolatile() {
+ return (Object)RESULT_HANDLE.getVolatile(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ private void propagateStack() {
+ Transform<?, T> topStack = (Transform<?, T>)COMPLETE_STACK_HANDLE.getAndSet(this, COMPLETED_STACK);
+ while (topStack != null) {
+ topStack.execute();
+ topStack = topStack.next;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ private static Object maskNull(final Object res) {
+ return res == null ? NULL_MASK : res;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
2024-10-25 21:24:15 +02:00
+ return failed(complete);
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
2024-10-25 21:24:15 +02:00
+ ret.completeExceptionally(complete);
+ }
2024-10-25 21:24:15 +02:00
+
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ public boolean complete(final T result) {
+ return this.completeRaw(maskNull(result));
+ }
+
2024-10-25 21:24:15 +02:00
+ public boolean completeExceptionally(final Throwable exception) {
+ Validate.notNull(exception, "Exception may not be null");
+
2024-10-25 21:24:15 +02:00
+ return this.completeRaw(new ExceptionResult(exception));
+ }
+
2024-10-25 21:24:15 +02:00
+ public boolean isDone() {
+ return this.getResultVolatile() != null;
+ }
+
2024-10-25 21:24:15 +02:00
+ public boolean isNormallyComplete() {
+ return this.getResultVolatile() != null && !(this.getResultVolatile() instanceof ExceptionResult);
+ }
+
2024-10-25 21:24:15 +02:00
+ public boolean isExceptionallyComplete() {
+ return this.getResultVolatile() instanceof ExceptionResult;
+ }
+
2024-10-25 21:24:15 +02:00
+ public Throwable getException() {
+ final Object res = this.getResultVolatile();
+ if (res == null) {
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (!(res instanceof ExceptionResult exRes)) {
+ throw new IllegalStateException("Not completed exceptionally");
+ }
+
2024-10-25 21:24:15 +02:00
+ return exRes.ex;
+ }
+
2024-10-25 21:24:15 +02:00
+ public T getNow(final T dfl) throws CompletionException {
+ final Object res = this.getResultVolatile();
+ if (res == null) {
+ return dfl;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (res instanceof ExceptionResult exRes) {
+ throw new CompletionException(exRes.ex);
+ }
+
2024-10-25 21:24:15 +02:00
+ return (T)unmaskNull(res);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
+
2024-10-25 21:24:15 +02:00
+ public <U> Completable<U> thenApply(final Function<? super T, ? extends U> function) {
+ return this.thenApply(function, DEFAULT_EXCEPTION_HANDLER);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ public <U> Completable<U> thenApplyAsync(final Function<? super T, ? extends U> function) {
+ return this.thenApplyAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
+ }
+
2024-10-25 21:24:15 +02:00
+ public <U> Completable<U> thenApplyAsync(final Function<? super T, ? extends U> function, final Executor executor) {
+ return this.thenApplyAsync(function, executor, DEFAULT_EXCEPTION_HANDLER);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+
+ public Completable<Void> thenAccept(final Consumer<? super T> consumer) {
+ return this.thenAccept(consumer, DEFAULT_EXCEPTION_HANDLER);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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");
+
2024-10-25 21:24:15 +02:00
+ final Completable<Void> ret = new Completable<>();
+ this.pushStackOrRun(new AcceptTransform<>(null, this, ret, exceptionHandler, consumer));
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ public Completable<Void> thenAcceptAsync(final Consumer<? super T> consumer) {
+ return this.thenAcceptAsync(consumer, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
+ }
+
2024-10-25 21:24:15 +02:00
+ public Completable<Void> thenAcceptAsync(final Consumer<? super T> consumer, final Executor executor) {
+ return this.thenAcceptAsync(consumer, executor, DEFAULT_EXCEPTION_HANDLER);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
+
2024-10-25 21:24:15 +02:00
+ public Completable<Void> thenRun(final Runnable run) {
+ return this.thenRun(run, DEFAULT_EXCEPTION_HANDLER);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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");
+
2024-10-25 21:24:15 +02:00
+ final Completable<Void> ret = new Completable<>();
+ this.pushStackOrRun(new RunTransform<>(null, this, ret, exceptionHandler, run));
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ public Completable<Void> thenRunAsync(final Runnable run) {
+ return this.thenRunAsync(run, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
+ }
+
2024-10-25 21:24:15 +02:00
+ public Completable<Void> thenRunAsync(final Runnable run, final Executor executor) {
+ return this.thenRunAsync(run, executor, DEFAULT_EXCEPTION_HANDLER);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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");
+
2024-10-25 21:24:15 +02:00
+ final Completable<Void> ret = new Completable<>();
+ this.pushStackOrRun(new RunTransform<>(checkExecutor(executor), this, ret, exceptionHandler, run));
+ return ret;
+ }
+
+
2024-10-25 21:24:15 +02:00
+ public <U> Completable<U> handle(final BiFunction<? super T, ? super Throwable, ? extends U> function) {
+ return this.handle(function, DEFAULT_EXCEPTION_HANDLER);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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");
+
2024-10-25 21:24:15 +02:00
+ final Completable<U> ret = new Completable<>();
+ this.pushStackOrRun(new HandleTransform<>(null, this, ret, exceptionHandler, function));
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ public <U> Completable<U> handleAsync(final BiFunction<? super T, ? super Throwable, ? extends U> function) {
+ return this.handleAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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");
+
2024-10-25 21:24:15 +02:00
+ final Completable<U> ret = new Completable<>();
+ this.pushStackOrRun(new HandleTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function));
+ return ret;
+ }
+
+
2024-10-25 21:24:15 +02:00
+ public Completable<T> whenComplete(final BiConsumer<? super T, ? super Throwable> consumer) {
+ return this.whenComplete(consumer, DEFAULT_EXCEPTION_HANDLER);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ public Completable<T> whenCompleteAsync(final BiConsumer<? super T, ? super Throwable> consumer) {
+ return this.whenCompleteAsync(consumer, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
+ }
+
2024-10-25 21:24:15 +02:00
+ public Completable<T> whenCompleteAsync(final BiConsumer<? super T, ? super Throwable> consumer, final Executor executor) {
+ return this.whenCompleteAsync(consumer, executor, DEFAULT_EXCEPTION_HANDLER);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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");
+
2024-10-25 21:24:15 +02:00
+ final Completable<T> ret = new Completable<>();
+ this.pushStackOrRun(new WhenTransform<>(checkExecutor(executor), this, ret, exceptionHandler, consumer));
+ return ret;
+ }
+
+
2024-10-25 21:24:15 +02:00
+ public Completable<T> exceptionally(final Function<Throwable, ? extends T> function) {
+ return this.exceptionally(function, DEFAULT_EXCEPTION_HANDLER);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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");
+
2024-10-25 21:24:15 +02:00
+ final Completable<T> ret = new Completable<>();
+ this.pushStackOrRun(new ExceptionallyTransform<>(null, this, ret, exceptionHandler, function));
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ public Completable<T> exceptionallyAsync(final Function<Throwable, ? extends T> function) {
+ return this.exceptionallyAsync(function, getDefaultExecutor(), DEFAULT_EXCEPTION_HANDLER);
+ }
+
2024-10-25 21:24:15 +02:00
+ public Completable<T> exceptionallyAsync(final Function<Throwable, ? extends T> function, final Executor executor) {
+ return this.exceptionallyAsync(function, executor, DEFAULT_EXCEPTION_HANDLER);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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");
+
2024-10-25 21:24:15 +02:00
+ final Completable<T> ret = new Completable<>();
+ this.pushStackOrRun(new ExceptionallyTransform<>(checkExecutor(executor), this, ret, exceptionHandler, function));
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ private static final class ExceptionResult {
+ public final Throwable ex;
+
2024-10-25 21:24:15 +02:00
+ public ExceptionResult(final Throwable ex) {
+ this.ex = ex;
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ private static abstract class Transform<U, T> implements Runnable, CompletableFuture.AsynchronousCompletionTask {
+
2024-10-25 21:24:15 +02:00
+ private Transform<?, T> next;
+
2024-10-25 21:24:15 +02:00
+ private final Executor executor;
+ protected final Completable<T> from;
+ protected final Completable<U> to;
+ protected final Function<? super Throwable, ? extends Throwable> exceptionHandler;
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ // force interface call to become virtual call
+ @Override
+ public abstract void run();
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ public void execute() {
+ if (this.executor == null) {
+ this.run();
+ return;
+ }
+
2024-10-25 21:24:15 +02:00
+ try {
+ this.executor.execute(this);
+ } catch (final Throwable throwable) {
+ this.failed(throwable);
+ }
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ @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);
+ }
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ @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);
+ }
+ }
2024-10-25 21:24:15 +02:00
+ }
+
+ private static final class RunTransform<T> extends Transform<Void, T> {
+ private final Runnable run;
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ @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);
+ }
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ private static final class HandleTransform<U, T> extends Transform<U, T> {
+
2024-10-25 21:24:15 +02:00
+ private final BiFunction<? super T, ? super Throwable, ? extends U> function;
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ @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);
+ }
+ }
2024-10-25 21:24:15 +02:00
+ }
+
+ private static final class WhenTransform<T> extends Transform<T, T> {
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ @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);
+ }
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ private static final class ExceptionallyTransform<T> extends Transform<T, T> {
+ private final Function<Throwable, ? extends T> function;
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ @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);
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ 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>
2024-10-25 21:24:15 +02:00
+ * 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>
2024-10-25 21:24:15 +02:00
+ * 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;
+
+ /**
2024-10-25 21:24:15 +02:00
+ * 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.
+ *
2024-10-25 21:24:15 +02:00
+ * @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
2024-10-25 21:24:15 +02:00
+ * associated with the parameter
+ */
2024-10-25 21:24:15 +02:00
+ public PrioritisedTask queueTask(final Runnable task);
+
+ /**
+ * Queues or executes a task.
+ *
+ * @param task The task to run.
+ * @param priority The priority for the task.
+ *
2024-10-25 21:24:15 +02:00
+ * @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
2024-10-25 21:24:15 +02:00
+ * associated with the parameter
+ */
2024-10-25 21:24:15 +02:00
+ public PrioritisedTask queueTask(final Runnable task, final Priority priority);
+
+ /**
2024-10-25 21:24:15 +02:00
+ * Queues or executes a task.
+ *
+ * @param task The task to run.
2024-10-25 21:24:15 +02:00
+ * @param priority The priority for the task.
+ * @param subOrder The task's suborder.
+ *
2024-10-25 21:24:15 +02:00
+ * @throws IllegalStateException If this executor has shutdown.
+ * @throws NullPointerException If the task is null
+ * @throws IllegalArgumentException If the priority is invalid.
2024-10-25 21:24:15 +02:00
+ * @return {@code null} if the current thread immediately executed the task, else returns the prioritised task
+ * associated with the parameter
+ */
2024-10-25 21:24:15 +02:00
+ 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);
+
+ /**
2024-10-25 21:24:15 +02:00
+ * 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.
2024-10-25 21:24:15 +02:00
+ * @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);
+
+ /**
2024-10-25 21:24:15 +02:00
+ * 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.
+ *
2024-10-25 21:24:15 +02:00
+ * @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
+ */
2024-10-25 21:24:15 +02:00
+ 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
2024-10-25 21:24:15 +02:00
+ * 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
2024-10-25 21:24:15 +02:00
+ * @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
2024-10-25 21:24:15 +02:00
+ * @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);
2024-10-25 21:24:15 +02:00
+
+ /**
+ * 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);
+ }
+}
2024-10-25 21:24:15 +02:00
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
2024-10-25 21:24:15 +02:00
index 0000000000000000000000000000000000000000..edb8c6611bdc9aced2714b963e00bbb7829603d2
--- /dev/null
2024-10-25 21:24:15 +02:00
+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/queue/PrioritisedTaskQueue.java
@@ -0,0 +1,454 @@
+package ca.spottedleaf.concurrentutil.executor.queue;
+
2024-10-25 21:24:15 +02:00
+import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor;
+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
2024-10-25 21:24:15 +02:00
+import ca.spottedleaf.concurrentutil.util.Priority;
+import java.lang.invoke.VarHandle;
2024-10-25 21:24:15 +02:00
+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;
+
2024-10-25 21:24:15 +02:00
+public final class PrioritisedTaskQueue implements PrioritisedExecutor {
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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);
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public long getTotalTasksScheduled() {
+ return this.scheduledTasks.get();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public long getTotalTasksExecuted() {
+ return this.executedTasks.get();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public long generateNextSubOrder() {
+ return this.subOrderGenerator.getAndIncrement();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean shutdown() {
+ return !this.shutdown.getAndSet(true);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean isShutdown() {
+ return this.shutdown.get();
+ }
+
2024-10-25 21:24:15 +02:00
+ public PrioritisedTask peekFirst() {
+ final Map.Entry<PrioritisedQueuedTask.Holder, Boolean> firstEntry = this.tasks.firstEntry();
+ return firstEntry == null ? null : firstEntry.getKey().task;
+ }
+
2024-10-25 21:24:15 +02:00
+ public Priority getHighestPriority() {
+ final Map.Entry<PrioritisedQueuedTask.Holder, Boolean> firstEntry = this.tasks.firstEntry();
+ return firstEntry == null ? null : Priority.getPriority(firstEntry.getKey().priority);
+ }
+
2024-10-25 21:24:15 +02:00
+ public boolean hasNoScheduledTasks() {
+ final long executedTasks = this.executedTasks.get();
+ final long scheduledTasks = this.scheduledTasks.get();
+
2024-10-25 21:24:15 +02:00
+ return executedTasks == scheduledTasks;
+ }
+
2024-10-25 21:24:15 +02:00
+ public PrioritySubOrderPair getHighestPrioritySubOrder() {
+ final Map.Entry<PrioritisedQueuedTask.Holder, Boolean> firstEntry = this.tasks.firstEntry();
+ if (firstEntry == null) {
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ final PrioritisedQueuedTask.Holder holder = firstEntry.getKey();
+
2024-10-25 21:24:15 +02:00
+ return new PrioritySubOrderPair(Priority.getPriority(holder.priority), holder.subOrder);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
2024-10-25 21:24:15 +02:00
+ return task.task.execute;
+ }
+
2024-10-25 21:24:15 +02:00
+ return null;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ @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;
+ }
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ return false;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public PrioritisedTask createTask(final Runnable task) {
+ return this.createTask(task, Priority.NORMAL, this.generateNextSubOrder());
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public PrioritisedTask createTask(final Runnable task, final Priority priority) {
+ return this.createTask(task, priority, this.generateNextSubOrder());
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) {
+ return new PrioritisedQueuedTask(task, priority, subOrder);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public PrioritisedTask queueTask(final Runnable task) {
+ return this.queueTask(task, Priority.NORMAL, this.generateNextSubOrder());
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public PrioritisedTask queueTask(final Runnable task, final Priority priority) {
+ return this.queueTask(task, priority, this.generateNextSubOrder());
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) {
+ final PrioritisedQueuedTask ret = new PrioritisedQueuedTask(task, priority, subOrder);
+
2024-10-25 21:24:15 +02:00
+ ret.queue();
+
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ final int subOrderCompare = Long.compare(t1.subOrder, t2.subOrder);
+ if (subOrderCompare != 0) {
+ return subOrderCompare;
+ }
+
2024-10-25 21:24:15 +02:00
+ return Long.compare(t1.id, t2.id);
+ };
+
2024-10-25 21:24:15 +02:00
+ private static final class Holder {
+ private final PrioritisedQueuedTask task;
+ private final int priority;
+ private final long subOrder;
+ private final long id;
+
2024-10-25 21:24:15 +02:00
+ private volatile boolean removed;
+ private static final VarHandle REMOVED_HANDLE = ConcurrentUtil.getVarHandle(Holder.class, "removed", boolean.class);
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * Returns true if marked as removed
+ */
+ public boolean markRemoved() {
+ return !(boolean)REMOVED_HANDLE.getAndSet((Holder)this, (boolean)true);
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ private final long id;
+ private final Runnable execute;
+
2024-10-25 21:24:15 +02:00
+ private Priority priority;
+ private long subOrder;
+ private Holder holder;
+
2024-10-25 21:24:15 +02:00
+ public PrioritisedQueuedTask(final Runnable execute, final Priority priority, final long subOrder) {
+ if (!Priority.isValidPriority(priority)) {
+ throw new IllegalArgumentException("Invalid priority " + priority);
+ }
+
2024-10-25 21:24:15 +02:00
+ this.execute = execute;
+ this.priority = priority;
+ this.subOrder = subOrder;
+ this.id = PrioritisedTaskQueue.this.taskIdGenerator.getAndIncrement();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public PrioritisedExecutor getExecutor() {
+ return PrioritisedTaskQueue.this;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean queue() {
+ synchronized (this) {
+ if (this.holder != null || this.priority == Priority.COMPLETING) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (PrioritisedTaskQueue.this.isShutdown()) {
+ throw new IllegalStateException("Queue is shutdown");
+ }
+
2024-10-25 21:24:15 +02:00
+ final Holder holder = new Holder(this, this.priority.priority, this.subOrder, this.id);
+ this.holder = holder;
+
2024-10-25 21:24:15 +02:00
+ PrioritisedTaskQueue.this.scheduledTasks.getAndIncrement();
+ PrioritisedTaskQueue.this.tasks.put(holder, Boolean.TRUE);
+ }
+
2024-10-25 21:24:15 +02:00
+ if (PrioritisedTaskQueue.this.isShutdown()) {
+ this.cancel();
+ throw new IllegalStateException("Queue is shutdown");
+ }
+
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean isQueued() {
+ synchronized (this) {
+ return this.holder != null && this.priority != Priority.COMPLETING;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean cancel() {
+ synchronized (this) {
+ if (this.priority == Priority.COMPLETING) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ this.priority = Priority.COMPLETING;
+
2024-10-25 21:24:15 +02:00
+ if (this.holder != null) {
+ if (this.holder.markRemoved()) {
+ PrioritisedTaskQueue.this.tasks.remove(this.holder);
+ }
+ PrioritisedTaskQueue.this.executedTasks.getAndIncrement();
+ }
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean execute() {
+ final boolean increaseExecuted;
+
2024-10-25 21:24:15 +02:00
+ synchronized (this) {
+ if (this.priority == Priority.COMPLETING) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ this.priority = Priority.COMPLETING;
+
2024-10-25 21:24:15 +02:00
+ if (increaseExecuted = (this.holder != null)) {
+ if (this.holder.markRemoved()) {
+ PrioritisedTaskQueue.this.tasks.remove(this.holder);
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ try {
+ this.execute.run();
+ return true;
+ } finally {
+ if (increaseExecuted) {
+ PrioritisedTaskQueue.this.executedTasks.getAndIncrement();
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public Priority getPriority() {
+ synchronized (this) {
+ return this.priority;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean setPriority(final Priority priority) {
+ synchronized (this) {
+ if (this.priority == Priority.COMPLETING || this.priority == priority) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ this.priority = priority;
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean raisePriority(final Priority priority) {
+ synchronized (this) {
+ if (this.priority == Priority.COMPLETING || this.priority.isHigherOrEqualPriority(priority)) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ this.priority = priority;
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean lowerPriority(Priority priority) {
+ synchronized (this) {
+ if (this.priority == Priority.COMPLETING || this.priority.isLowerOrEqualPriority(priority)) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ this.priority = priority;
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public long getSubOrder() {
+ synchronized (this) {
+ return this.subOrder;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean setSubOrder(final long subOrder) {
+ synchronized (this) {
+ if (this.priority == Priority.COMPLETING || this.subOrder == subOrder) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ this.subOrder = subOrder;
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean raiseSubOrder(long subOrder) {
+ synchronized (this) {
+ if (this.priority == Priority.COMPLETING || this.subOrder >= subOrder) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ this.subOrder = subOrder;
+
2024-10-25 21:24:15 +02:00
+ if (this.holder != null) {
+ if (this.holder.markRemoved()) {
+ PrioritisedTaskQueue.this.tasks.remove(this.holder);
+ }
2024-10-25 21:24:15 +02:00
+ this.holder = new Holder(this, priority.priority, this.subOrder, this.id);
+ PrioritisedTaskQueue.this.tasks.put(this.holder, Boolean.TRUE);
+ }
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean lowerSubOrder(final long subOrder) {
+ synchronized (this) {
+ if (this.priority == Priority.COMPLETING || this.subOrder <= subOrder) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ this.subOrder = subOrder;
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ @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;
+ }
+
2024-10-25 21:24:15 +02:00
+ this.priority = priority;
+ this.subOrder = subOrder;
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+
2024-10-25 21:24:15 +02:00
+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;
+
2024-10-25 21:24:15 +02:00
+/**
+ * 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 {
+
2024-10-25 21:24:15 +02:00
+ private static final Logger LOGGER = LoggerFactory.getLogger(PrioritisedQueueExecutorThread.class);
+
2024-10-25 21:24:15 +02:00
+ protected final PrioritisedExecutor queue;
+
2024-10-25 21:24:15 +02:00
+ protected volatile boolean threadShutdown;
+
2024-10-25 21:24:15 +02:00
+ protected volatile boolean threadParked;
+ protected static final VarHandle THREAD_PARKED_HANDLE = ConcurrentUtil.getVarHandle(PrioritisedQueueExecutorThread.class, "threadParked", boolean.class);
+
2024-10-25 21:24:15 +02:00
+ protected volatile boolean halted;
+
2024-10-25 21:24:15 +02:00
+ protected final long spinWaitTime;
+
2024-10-25 21:24:15 +02:00
+ protected static final long DEFAULT_SPINWAIT_TIME = (long)(0.1e6);// 0.1ms
+
2024-10-25 21:24:15 +02:00
+ public PrioritisedQueueExecutorThread(final PrioritisedExecutor queue) {
+ this(queue, DEFAULT_SPINWAIT_TIME); // 0.1ms
+ }
+
2024-10-25 21:24:15 +02:00
+ public PrioritisedQueueExecutorThread(final PrioritisedExecutor queue, final long spinWaitTime) { // in ns
+ this.queue = queue;
+ this.spinWaitTime = spinWaitTime;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public final void run() {
+ try {
+ this.begin();
+ this.doRun();
+ } finally {
+ this.die();
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ public final void doRun() {
+ final long spinWaitTime = this.spinWaitTime;
+
2024-10-25 21:24:15 +02:00
+ main_loop:
+ for (;;) {
+ this.pollTasks();
+
2024-10-25 21:24:15 +02:00
+ // spinwait
+
2024-10-25 21:24:15 +02:00
+ final long start = System.nanoTime();
+
2024-10-25 21:24:15 +02:00
+ 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
+
2024-10-25 21:24:15 +02:00
+ if (this.pollTasks()) {
+ // restart loop, found tasks
+ continue main_loop;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (this.handleClose()) {
+ return; // we're done
+ }
+
2024-10-25 21:24:15 +02:00
+ if ((System.nanoTime() - start) >= spinWaitTime) {
+ break;
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ if (this.handleClose()) {
+ return;
+ }
+
2024-10-25 21:24:15 +02:00
+ this.setThreadParkedVolatile(true);
+
2024-10-25 21:24:15 +02:00
+ // 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (this.handleClose()) {
+ return;
+ }
+
2024-10-25 21:24:15 +02:00
+ // 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");
+ }
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ protected void begin() {}
+
2024-10-25 21:24:15 +02:00
+ protected void die() {}
+
2024-10-25 21:24:15 +02:00
+ /**
+ * Attempts to poll as many tasks as possible, returning when finished.
+ * @return Whether any tasks were executed.
+ */
+ protected boolean pollTasks() {
+ boolean ret = false;
+
2024-10-25 21:24:15 +02:00
+ for (;;) {
+ if (this.halted) {
+ break;
+ }
2024-10-25 21:24:15 +02:00
+ try {
+ if (!this.queue.executeTask()) {
+ break;
+ }
2024-10-25 21:24:15 +02:00
+ ret = true;
+ } catch (final Throwable throwable) {
+ LOGGER.error("Exception thrown from prioritized runnable task in thread '" + this.getName() + "'", throwable);
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ protected boolean handleClose() {
+ if (this.threadShutdown) {
+ this.pollTasks(); // this ensures we've emptied the queue
+ return true;
+ }
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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;
+ }
2024-10-25 21:24:15 +02:00
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public long getTotalTasksExecuted() {
+ return this.queue.getTotalTasksExecuted();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public long getTotalTasksScheduled() {
+ return this.queue.getTotalTasksScheduled();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public long generateNextSubOrder() {
+ return this.queue.generateNextSubOrder();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean shutdown() {
+ throw new UnsupportedOperationException();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean isShutdown() {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ * @throws IllegalStateException Always
+ */
+ @Override
+ public boolean executeTask() throws IllegalStateException {
+ throw new IllegalStateException();
+ }
+
+ @Override
2024-10-25 21:24:15 +02:00
+ public PrioritisedTask queueTask(final Runnable task) {
+ final PrioritisedTask ret = this.createTask(task);
+
2024-10-25 21:24:15 +02:00
+ ret.queue();
+
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public PrioritisedTask queueTask(final Runnable task, final Priority priority) {
+ final PrioritisedTask ret = this.createTask(task, priority);
+
2024-10-25 21:24:15 +02:00
+ ret.queue();
+
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) {
+ final PrioritisedTask ret = this.createTask(task, priority, subOrder);
+
2024-10-25 21:24:15 +02:00
+ ret.queue();
+
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+
+ @Override
2024-10-25 21:24:15 +02:00
+ public PrioritisedTask createTask(Runnable task) {
+ final PrioritisedTask queueTask = this.queue.createTask(task);
+
2024-10-25 21:24:15 +02:00
+ return new WrappedTask(queueTask);
+ }
+
+ @Override
2024-10-25 21:24:15 +02:00
+ public PrioritisedTask createTask(final Runnable task, final Priority priority) {
+ final PrioritisedTask queueTask = this.queue.createTask(task, priority);
+
+ return new WrappedTask(queueTask);
+ }
+
+ @Override
2024-10-25 21:24:15 +02:00
+ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) {
+ final PrioritisedTask queueTask = this.queue.createTask(task, priority, subOrder);
+
2024-10-25 21:24:15 +02:00
+ return new WrappedTask(queueTask);
+ }
+
+ /**
2024-10-25 21:24:15 +02:00
+ * 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)
+ */
2024-10-25 21:24:15 +02:00
+ public boolean close(final boolean wait, final boolean killQueue) {
+ final boolean ret = killQueue && this.queue.shutdown();
+ this.threadShutdown = true;
+
2024-10-25 21:24:15 +02:00
+ // 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();
+ }
2024-10-25 21:24:15 +02:00
+ break;
+ }
+ try {
+ this.join();
+ } catch (final InterruptedException ex) {
+ interrupted = true;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+
+ /**
2024-10-25 21:24:15 +02:00
+ * 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)
+ */
2024-10-25 21:24:15 +02:00
+ public void halt(final boolean killQueue) {
+ if (killQueue) {
+ this.queue.shutdown();
+ }
2024-10-25 21:24:15 +02:00
+ this.threadShutdown = true;
+ this.halted = true;
+
2024-10-25 21:24:15 +02:00
+ // force thread to respond to the shutdown
+ this.setThreadParkedVolatile(false);
+ LockSupport.unpark(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final boolean getThreadParkedVolatile() {
+ return (boolean)THREAD_PARKED_HANDLE.getVolatile(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final boolean exchangeThreadParkedVolatile(final boolean value) {
+ return (boolean)THREAD_PARKED_HANDLE.getAndSet(this, value);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setThreadParkedVolatile(final boolean value) {
+ THREAD_PARKED_HANDLE.setVolatile(this, value);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * Required so that queue() can notify (unpark) this thread
+ */
+ private final class WrappedTask implements PrioritisedTask {
+ private final PrioritisedTask queueTask;
+
2024-10-25 21:24:15 +02:00
+ public WrappedTask(final PrioritisedTask queueTask) {
+ this.queueTask = queueTask;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public PrioritisedExecutor getExecutor() {
+ return PrioritisedQueueExecutorThread.this;
+ }
+
+ @Override
+ public boolean queue() {
2024-10-25 21:24:15 +02:00
+ final boolean ret = this.queueTask.queue();
+ if (ret) {
+ PrioritisedQueueExecutorThread.this.notifyTasks();
+ }
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean isQueued() {
+ return this.queueTask.isQueued();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean cancel() {
+ return this.queueTask.cancel();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean execute() {
+ return this.queueTask.execute();
+ }
+
+ @Override
+ public Priority getPriority() {
2024-10-25 21:24:15 +02:00
+ return this.queueTask.getPriority();
+ }
+
+ @Override
+ public boolean setPriority(final Priority priority) {
2024-10-25 21:24:15 +02:00
+ return this.queueTask.setPriority(priority);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean raisePriority(final Priority priority) {
+ return this.queueTask.raisePriority(priority);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean lowerPriority(final Priority priority) {
+ return this.queueTask.lowerPriority(priority);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public long getSubOrder() {
+ return this.queueTask.getSubOrder();
+ }
+
+ @Override
2024-10-25 21:24:15 +02:00
+ public boolean setSubOrder(final long subOrder) {
+ return this.queueTask.setSubOrder(subOrder);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean raiseSubOrder(final long subOrder) {
+ return this.queueTask.raiseSubOrder(subOrder);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean lowerSubOrder(final long subOrder) {
+ return this.queueTask.lowerSubOrder(subOrder);
+ }
+
2024-10-25 21:24:15 +02:00
+ @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;
+
2024-10-25 21:24:15 +02:00
+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;
+
2024-10-25 21:24:15 +02:00
+public final class PrioritisedThreadPool {
+
2024-10-25 21:24:15 +02:00
+ 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");
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ public Thread[] getAliveThreads() {
+ final PrioritisedThread[] threads = this.aliveThreads.getArray();
+
2024-10-25 21:24:15 +02:00
+ return Arrays.copyOf(threads, threads.length, Thread[].class);
+ }
+
2024-10-25 21:24:15 +02:00
+ public Thread[] getCoreThreads() {
+ final PrioritisedThread[] threads = this.threads.getArray();
+
2024-10-25 21:24:15 +02:00
+ return Arrays.copyOf(threads, threads.length, Thread[].class);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * Prevents creation of new queues, shutdowns all non-shutdown queues if specified
+ */
+ public void halt(final boolean shutdownQueues) {
+ synchronized (this) {
+ this.shutdown = true;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (shutdownQueues) {
+ for (final ExecutorGroup group : this.executors.getArray()) {
+ for (final ExecutorGroup.ThreadPoolExecutor executor : group.executors.getArray()) {
+ executor.shutdown();
+ }
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ for (final PrioritisedThread thread : this.threads.getArray()) {
+ thread.halt(false);
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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);
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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;
+ }
+ }
+ }
2024-10-25 21:24:15 +02:00
+
+ return true;
2024-10-25 21:24:15 +02:00
+ } finally {
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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();
+ }
+ }
+
+
2024-10-25 21:24:15 +02:00
+ for (final PrioritisedThread thread : this.threads.getArray()) {
+ // none of these can be true or else NPE
+ thread.close(false, false);
+ }
+
2024-10-25 21:24:15 +02:00
+ if (wait) {
+ this.join(0L);
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ private void die(final PrioritisedThread thread) {
+ this.aliveThreads.remove(thread);
+ }
+
2024-10-25 21:24:15 +02:00
+ public void adjustThreadCount(final int threads) {
+ synchronized (this) {
+ if (this.shutdown) {
+ return;
+ }
+
2024-10-25 21:24:15 +02:00
+ final PrioritisedThread[] currentThreads = this.threads.getArray();
+ if (threads == currentThreads.length) {
+ // no adjustment needed
+ return;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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];
+
2024-10-25 21:24:15 +02:00
+ 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();
+
2024-10-25 21:24:15 +02:00
+ this.threadModifier.accept(thread);
+ this.aliveThreads.add(thread);
+ this.threads.add(thread);
+
2024-10-25 21:24:15 +02:00
+ thread.start();
+ }
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ final int parallelismCompare = src.currentParallelism - dst.currentParallelism;
+ if (parallelismCompare != 0) {
+ return parallelismCompare;
+ }
+
2024-10-25 21:24:15 +02:00
+ return TimeUtil.compareTimes(src.lastRetrieved, dst.lastRetrieved);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ final int parallelismCompare = src.getGroup().currentParallelism - dst.getGroup().currentParallelism;
+ if (parallelismCompare != 0) {
+ return parallelismCompare;
+ }
+
2024-10-25 21:24:15 +02:00
+ return TimeUtil.compareTimes(src.lastRetrieved, dst.lastRetrieved);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ final Priority priority = executor.getTargetPriority();
+
2024-10-25 21:24:15 +02:00
+ if (priority == null) {
+ continue;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (highestPriority == null || compareInsideGroup(highest, highestPriority, executor, priority) > 0) {
+ highest = executor;
+ highestPriority = priority;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ if (highest == null) {
+ continue;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (ret == null || compareOutsideGroup(ret, retPriority, highest, highestPriority) > 0) {
+ ret = highest;
+ retPriority = highestPriority;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ if (ret != null) {
+ ret.lastRetrieved = time;
+ ++ret.currentParallelism;
+ ++ret.getGroup().currentParallelism;
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ private void notifyAllThreads() {
+ for (final PrioritisedThread thread : this.threads.getArray()) {
+ thread.notifyTasks();
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ public ExecutorGroup createExecutorGroup(final int division, final int flags) {
+ synchronized (this) {
+ if (this.shutdown) {
+ throw new IllegalStateException("Queue is shutdown: " + this.toString());
+ }
+
2024-10-25 21:24:15 +02:00
+ final ExecutorGroup ret = new ExecutorGroup(division, flags);
+
2024-10-25 21:24:15 +02:00
+ this.executors.add(ret);
+
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ private final class PrioritisedThread extends PrioritisedQueueExecutorThread {
+
2024-10-25 21:24:15 +02:00
+ private final AtomicBoolean alertedHighPriority = new AtomicBoolean();
+
2024-10-25 21:24:15 +02:00
+ public PrioritisedThread() {
+ super(null);
+ }
+
2024-10-25 21:24:15 +02:00
+ public boolean alertHighPriorityExecutor() {
+ if (!this.notifyTasks()) {
+ if (!this.alertedHighPriority.get()) {
+ this.alertedHighPriority.set(true);
+ }
2024-10-25 21:24:15 +02:00
+ return false;
+ }
2024-10-25 21:24:15 +02:00
+
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ private boolean isAlertedHighPriority() {
+ return this.alertedHighPriority.get() && this.alertedHighPriority.getAndSet(false);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ protected void die() {
+ PrioritisedThreadPool.this.die(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ protected boolean pollTasks() {
+ boolean ret = false;
+
+ for (;;) {
+ if (this.halted) {
+ break;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+
2024-10-25 21:24:15 +02:00
+ PrioritisedThreadPool.this.returnQueue(executor);
+ }
+
+
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ public final class ExecutorGroup {
+
2024-10-25 21:24:15 +02:00
+ private final AtomicLong subOrderGenerator = new AtomicLong();
+ private final COWArrayList<ThreadPoolExecutor> executors = new COWArrayList<>(ThreadPoolExecutor.class);
+
2024-10-25 21:24:15 +02:00
+ private final int division;
+ private int currentParallelism;
+
2024-10-25 21:24:15 +02:00
+ private ExecutorGroup(final int division, final int flags) {
+ this.division = division;
+ }
+
2024-10-25 21:24:15 +02:00
+ public ThreadPoolExecutor[] getAllExecutors() {
+ return this.executors.getArray().clone();
+ }
+
2024-10-25 21:24:15 +02:00
+ private PrioritisedThreadPool getThreadPool() {
+ return PrioritisedThreadPool.this;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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());
+ }
+
2024-10-25 21:24:15 +02:00
+ final ThreadPoolExecutor ret = new ThreadPoolExecutor(maxParallelism, queueMaxHoldTime, flags);
+
2024-10-25 21:24:15 +02:00
+ this.executors.add(ret);
+
+ return ret;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ public final class ThreadPoolExecutor implements PrioritisedExecutor {
+
2024-10-25 21:24:15 +02:00
+ private final PrioritisedTaskQueue queue = new PrioritisedTaskQueue();
+
2024-10-25 21:24:15 +02:00
+ private volatile int maxParallelism;
+ private final long queueMaxHoldTime;
+ private volatile int currentParallelism;
+ private volatile boolean halt;
+ private long lastRetrieved = System.nanoTime();
+
2024-10-25 21:24:15 +02:00
+ private ThreadPoolExecutor(final int maxParallelism, final long queueMaxHoldTime, final int flags) {
+ this.maxParallelism = maxParallelism;
+ this.queueMaxHoldTime = queueMaxHoldTime;
+ }
+
2024-10-25 21:24:15 +02:00
+ private ExecutorGroup getGroup() {
+ return ExecutorGroup.this;
+ }
+
2024-10-25 21:24:15 +02:00
+ private boolean canNotify() {
+ if (this.halt) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ final int max = this.maxParallelism;
+ return max < 0 || this.currentParallelism < max;
+ }
+
2024-10-25 21:24:15 +02:00
+ private void notifyHighPriority() {
+ if (!this.canNotify()) {
+ return;
+ }
+ for (final PrioritisedThread thread : this.getGroup().getThreadPool().threads.getArray()) {
+ if (thread.alertHighPriorityExecutor()) {
+ return;
+ }
2024-10-25 21:24:15 +02:00
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ private void notifyScheduled() {
+ if (!this.canNotify()) {
+ return;
+ }
+ for (final PrioritisedThread thread : this.getGroup().getThreadPool().threads.getArray()) {
+ if (thread.notifyTasks()) {
+ return;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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;
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
2024-10-25 21:24:15 +02:00
+
+ return !this.queue.hasNoScheduledTasks();
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean shutdown() {
+ if (!this.queue.shutdown()) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (this.queue.hasNoScheduledTasks()) {
+ ExecutorGroup.this.executors.remove(this);
+ }
2024-10-25 21:24:15 +02:00
+
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ @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();
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ Priority getTargetPriority() {
+ final Priority ret = this.queue.getHighestPriority();
+ if (!this.isShutdown()) {
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ return ret == null ? QUEUE_SHUTDOWN_PRIORITY : Priority.max(ret, QUEUE_SHUTDOWN_PRIORITY);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public long getTotalTasksScheduled() {
+ return this.queue.getTotalTasksScheduled();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public long getTotalTasksExecuted() {
+ return this.queue.getTotalTasksExecuted();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public long generateNextSubOrder() {
+ return ExecutorGroup.this.subOrderGenerator.getAndIncrement();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean executeTask() {
+ return this.queue.executeTask();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public PrioritisedTask queueTask(final Runnable task) {
+ final PrioritisedTask ret = this.createTask(task);
+
2024-10-25 21:24:15 +02:00
+ ret.queue();
+
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public PrioritisedTask queueTask(final Runnable task, final Priority priority) {
+ final PrioritisedTask ret = this.createTask(task, priority);
+
2024-10-25 21:24:15 +02:00
+ ret.queue();
+
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public PrioritisedTask queueTask(final Runnable task, final Priority priority, final long subOrder) {
+ final PrioritisedTask ret = this.createTask(task, priority, subOrder);
+
2024-10-25 21:24:15 +02:00
+ ret.queue();
+
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public PrioritisedTask createTask(final Runnable task) {
+ return this.createTask(task, Priority.NORMAL);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public PrioritisedTask createTask(final Runnable task, final Priority priority) {
+ return this.createTask(task, priority, this.generateNextSubOrder());
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public PrioritisedTask createTask(final Runnable task, final Priority priority, final long subOrder) {
+ return new WrappedTask(this.queue.createTask(task, priority, subOrder));
+ }
+
2024-10-25 21:24:15 +02:00
+ private final class WrappedTask implements PrioritisedTask {
+
2024-10-25 21:24:15 +02:00
+ private final PrioritisedTask wrapped;
+
2024-10-25 21:24:15 +02:00
+ private WrappedTask(final PrioritisedTask wrapped) {
+ this.wrapped = wrapped;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public PrioritisedExecutor getExecutor() {
+ return ThreadPoolExecutor.this;
+ }
+
2024-10-25 21:24:15 +02:00
+ @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;
+ }
+
2024-10-25 21:24:15 +02:00
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean isQueued() {
+ return this.wrapped.isQueued();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean cancel() {
+ return this.wrapped.cancel();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean execute() {
+ return this.wrapped.execute();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public Priority getPriority() {
+ return this.wrapped.getPriority();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean setPriority(final Priority priority) {
+ if (this.wrapped.setPriority(priority)) {
+ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) {
+ ThreadPoolExecutor.this.notifyHighPriority();
+ }
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean raisePriority(final Priority priority) {
+ if (this.wrapped.raisePriority(priority)) {
+ if (priority.isHigherOrEqualPriority(HIGH_PRIORITY_NOTIFY_THRESHOLD)) {
+ ThreadPoolExecutor.this.notifyHighPriority();
+ }
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean lowerPriority(final Priority priority) {
+ return this.wrapped.lowerPriority(priority);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public long getSubOrder() {
+ return this.wrapped.getSubOrder();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean setSubOrder(final long subOrder) {
+ return this.wrapped.setSubOrder(subOrder);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean raiseSubOrder(final long subOrder) {
+ return this.wrapped.raiseSubOrder(subOrder);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean lowerSubOrder(final long subOrder) {
+ return this.wrapped.lowerSubOrder(subOrder);
+ }
+
2024-10-25 21:24:15 +02:00
+ @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;
+ }
+
2024-10-25 21:24:15 +02:00
+ return false;
+ }
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ private static final class COWArrayList<E> {
+
2024-10-25 21:24:15 +02:00
+ private volatile E[] array;
+
2024-10-25 21:24:15 +02:00
+ public COWArrayList(final Class<E> clazz) {
+ this.array = (E[])Array.newInstance(clazz, 0);
+ }
+
2024-10-25 21:24:15 +02:00
+ public E[] getArray() {
+ return this.array;
+ }
+
2024-10-25 21:24:15 +02:00
+ public void add(final E element) {
+ synchronized (this) {
+ final E[] array = this.array;
+
2024-10-25 21:24:15 +02:00
+ final E[] copy = Arrays.copyOf(array, array.length + 1);
+ copy[array.length] = element;
+
2024-10-25 21:24:15 +02:00
+ this.array = copy;
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ if (index == -1) {
+ return false;
+ }
2024-10-25 21:24:15 +02:00
+
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+ }
2024-10-25 21:24:15 +02:00
+}
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;
+
2024-10-25 21:24:15 +02:00
+@FunctionalInterface
+public interface BiLong1Function<T, R> {
+
2024-10-25 21:24:15 +02:00
+ public R apply(final long t1, final T t2);
+
2024-10-25 21:24:15 +02:00
+}
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;
+
2024-10-25 21:24:15 +02:00
+@FunctionalInterface
+public interface BiLongObjectConsumer<V> {
+
2024-10-25 21:24:15 +02:00
+ public void accept(final long key, final V value);
+
2024-10-25 21:24:15 +02:00
+}
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;
+
2024-10-25 21:24:15 +02:00
+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;
+
2024-10-25 21:24:15 +02:00
+public final class ReentrantAreaLock {
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+
2024-10-25 21:24:15 +02:00
+ final long coordinate = IntPairUtil.key(sectionX, sectionZ);
+ final Node node = this.nodes.get(coordinate);
+
2024-10-25 21:24:15 +02:00
+ return node != null && node.thread == currThread;
+ }
+
2024-10-25 21:24:15 +02:00
+ public boolean isHeldByCurrentThread(final int centerX, final int centerZ, final int radius) {
+ return this.isHeldByCurrentThread(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius);
+ }
+
2024-10-25 21:24:15 +02:00
+ public boolean isHeldByCurrentThread(final int fromX, final int fromZ, final int toX, final int toZ) {
+ if (fromX > toX || fromZ > toZ) {
+ throw new IllegalArgumentException();
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+
2024-10-25 21:24:15 +02:00
+ for (int currZ = fromSectionZ; currZ <= toSectionZ; ++currZ) {
+ for (int currX = fromSectionX; currX <= toSectionX; ++currX) {
+ final long coordinate = IntPairUtil.key(currX, currZ);
+
2024-10-25 21:24:15 +02:00
+ final Node node = this.nodes.get(coordinate);
+
2024-10-25 21:24:15 +02:00
+ if (node == null || node.thread != currThread) {
+ return false;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ public Node tryLock(final int x, final int z) {
+ return this.tryLock(x, z, x, z);
+ }
+
2024-10-25 21:24:15 +02:00
+ public Node tryLock(final int centerX, final int centerZ, final int radius) {
+ return this.tryLock(centerX - radius, centerZ - radius, centerX + radius, centerZ + radius);
+ }
+
2024-10-25 21:24:15 +02:00
+ public Node tryLock(final int fromX, final int fromZ, final int toX, final int toZ) {
+ if (fromX > toX || fromZ > toZ) {
+ throw new IllegalArgumentException();
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+
2024-10-25 21:24:15 +02:00
+ final long[] areaAffected = new long[(toSectionX - fromSectionX + 1) * (toSectionZ - fromSectionZ + 1)];
+ int areaAffectedLen = 0;
+
2024-10-25 21:24:15 +02:00
+ final Node ret = new Node(this, areaAffected, currThread);
+
2024-10-25 21:24:15 +02:00
+ boolean failed = false;
+
2024-10-25 21:24:15 +02:00
+ // 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);
+
2024-10-25 21:24:15 +02:00
+ final Node prev = this.nodes.putIfAbsent(coordinate, ret);
+
2024-10-25 21:24:15 +02:00
+ if (prev == null) {
+ areaAffected[areaAffectedLen++] = coordinate;
+ continue;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (prev.thread != currThread) {
+ failed = true;
+ break;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ if (!failed) {
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ // failed, undo logic
+ if (areaAffectedLen != 0) {
+ for (int i = 0; i < areaAffectedLen; ++i) {
+ final long key = areaAffected[i];
+
2024-10-25 21:24:15 +02:00
+ if (this.nodes.remove(key) != ret) {
+ throw new IllegalStateException();
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ areaAffectedLen = 0;
+
2024-10-25 21:24:15 +02:00
+ // since we inserted, we need to drain waiters
+ Thread unpark;
+ while ((unpark = ret.pollOrBlockAdds()) != null) {
+ LockSupport.unpark(unpark);
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+
2024-10-25 21:24:15 +02:00
+ final long coordinate = IntPairUtil.key(sectionX, sectionZ);
+ final long[] areaAffected = new long[1];
+ areaAffected[0] = coordinate;
+
2024-10-25 21:24:15 +02:00
+ final Node ret = new Node(this, areaAffected, currThread);
+
2024-10-25 21:24:15 +02:00
+ for (long failures = 0L;;) {
+ final Node park;
+
2024-10-25 21:24:15 +02:00
+ // try to fast acquire area
+ {
+ final Node prev = this.nodes.putIfAbsent(coordinate, ret);
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ ++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;
+ }
+ }
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ 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();
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+
2024-10-25 21:24:15 +02:00
+ if (((fromSectionX ^ toSectionX) | (fromSectionZ ^ toSectionZ)) == 0) {
+ return this.lock(fromX, fromZ);
+ }
+
2024-10-25 21:24:15 +02:00
+ final long[] areaAffected = new long[(toSectionX - fromSectionX + 1) * (toSectionZ - fromSectionZ + 1)];
+ int areaAffectedLen = 0;
+
2024-10-25 21:24:15 +02:00
+ final Node ret = new Node(this, areaAffected, currThread);
+
2024-10-25 21:24:15 +02:00
+ for (long failures = 0L;;) {
+ Node park = null;
+ boolean addedToArea = false;
+ boolean alreadyOwned = false;
+ boolean allOwned = true;
+
2024-10-25 21:24:15 +02:00
+ // 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);
+
2024-10-25 21:24:15 +02:00
+ final Node prev = this.nodes.putIfAbsent(coordinate, ret);
+
2024-10-25 21:24:15 +02:00
+ if (prev == null) {
+ addedToArea = true;
+ allOwned = false;
+ areaAffected[areaAffectedLen++] = coordinate;
+ continue;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (prev.thread != currThread) {
+ park = prev;
+ alreadyOwned = true;
+ break;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ // 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];
+
2024-10-25 21:24:15 +02:00
+ if (this.nodes.remove(key) != ret) {
+ throw new IllegalStateException();
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ areaAffectedLen = 0;
+
2024-10-25 21:24:15 +02:00
+ // since we inserted, we need to drain waiters
+ Thread unpark;
+ while ((unpark = ret.pollOrBlockAdds()) != null) {
+ LockSupport.unpark(unpark);
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ if (park == null) {
+ if (alreadyOwned && !allOwned) {
+ throw new IllegalStateException("Improper lock usage: Should never acquire intersecting areas");
+ }
2024-10-25 21:24:15 +02:00
+ ret.areaAffectedLen = areaAffectedLen;
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ // failed
+
2024-10-25 21:24:15 +02:00
+ ++failures;
+
2024-10-25 21:24:15 +02:00
+ 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();
+ }
2024-10-25 21:24:15 +02:00
+ 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;
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ if (addedToArea) {
+ // try again, so we need to allow adds so that other threads can properly block on us
+ ret.allowAdds();
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ public void unlock(final Node node) {
+ if (node.lock != this) {
+ throw new IllegalStateException("Unlock target lock mismatch");
+ }
+
2024-10-25 21:24:15 +02:00
+ final long[] areaAffected = node.areaAffected;
+ final int areaAffectedLen = node.areaAffectedLen;
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ Objects.checkFromToIndex(0, areaAffectedLen, areaAffected.length);
+
2024-10-25 21:24:15 +02:00
+ // 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();
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ Thread unpark;
+ while ((unpark = node.pollOrBlockAdds()) != null) {
+ LockSupport.unpark(unpark);
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ public static final class Node extends MultiThreadedQueue<Thread> {
+
2024-10-25 21:24:15 +02:00
+ private final ReentrantAreaLock lock;
+ private final long[] areaAffected;
+ private int areaAffectedLen;
+ private final Thread thread;
+
2024-10-25 21:24:15 +02:00
+ private Node(final ReentrantAreaLock lock, final long[] areaAffected, final Thread thread) {
+ this.lock = lock;
+ this.areaAffected = areaAffected;
+ this.thread = thread;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public String toString() {
+ return "Node{" +
+ "areaAffected=" + IntPairUtil.toString(this.areaAffected, 0, this.areaAffectedLen) +
+ ", thread=" + this.thread +
+ '}';
+ }
+ }
2024-10-25 21:24:15 +02:00
+}
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;
+
2024-10-25 21:24:15 +02:00
+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;
+
2024-10-25 21:24:15 +02:00
+/**
+ * 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>> {
+
2024-10-25 21:24:15 +02:00
+ 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;
+
2024-10-25 21:24:15 +02:00
+ protected final LongAdder size = new LongAdder();
+ protected final float loadFactor;
+
2024-10-25 21:24:15 +02:00
+ protected volatile TableEntry<V>[] table;
+
2024-10-25 21:24:15 +02:00
+ 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);
+
2024-10-25 21:24:15 +02:00
+ protected final int getThresholdAcquire() {
+ return (int)THRESHOLD_HANDLE.getAcquire(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final int getThresholdVolatile() {
+ return (int)THRESHOLD_HANDLE.getVolatile(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setThresholdPlain(final int threshold) {
+ THRESHOLD_HANDLE.set(this, threshold);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setThresholdRelease(final int threshold) {
+ THRESHOLD_HANDLE.setRelease(this, threshold);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setThresholdVolatile(final int threshold) {
+ THRESHOLD_HANDLE.setVolatile(this, threshold);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
2024-10-25 21:24:15 +02:00
+
+ return (int)Math.ceil(ret);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected ConcurrentLong2ReferenceChainedHashTable(final int capacity, final float loadFactor) {
+ final int tableSize = getCapacityFor(capacity);
+
2024-10-25 21:24:15 +02:00
+ if (loadFactor <= 0.0 || !Float.isFinite(loadFactor)) {
+ throw new IllegalArgumentException("Invalid load factor: " + loadFactor);
+ }
+
2024-10-25 21:24:15 +02:00
+ if (tableSize == MAXIMUM_CAPACITY) {
+ this.setThresholdPlain(THRESHOLD_NO_RESIZE);
+ } else {
+ this.setThresholdPlain(getTargetThreshold(tableSize, loadFactor));
+ }
+
2024-10-25 21:24:15 +02:00
+ this.loadFactor = loadFactor;
+ // noinspection unchecked
+ this.table = (TableEntry<V>[])new TableEntry[tableSize];
+ }
+
2024-10-25 21:24:15 +02:00
+ public static <V> ConcurrentLong2ReferenceChainedHashTable<V> createWithCapacity(final int capacity) {
+ return createWithCapacity(capacity, DEFAULT_LOAD_FACTOR);
+ }
+
2024-10-25 21:24:15 +02:00
+ public static <V> ConcurrentLong2ReferenceChainedHashTable<V> createWithCapacity(final int capacity, final float loadFactor) {
+ return new ConcurrentLong2ReferenceChainedHashTable<>(capacity, loadFactor);
+ }
+
2024-10-25 21:24:15 +02:00
+ public static <V> ConcurrentLong2ReferenceChainedHashTable<V> createWithExpected(final int expected) {
+ return createWithExpected(expected, DEFAULT_LOAD_FACTOR);
+ }
+
2024-10-25 21:24:15 +02:00
+ public static <V> ConcurrentLong2ReferenceChainedHashTable<V> createWithExpected(final int expected, final float loadFactor) {
+ final int capacity = (int)Math.ceil((double)expected / (double)loadFactor);
+
2024-10-25 21:24:15 +02:00
+ return createWithCapacity(capacity, loadFactor);
+ }
+
2024-10-25 21:24:15 +02:00
+ /** must be deterministic given a key */
+ protected static int getHash(final long key) {
+ return (int)HashUtil.mix(key);
+ }
+
+ /**
2024-10-25 21:24:15 +02:00
+ * Returns the load factor associated with this map.
+ */
2024-10-25 21:24:15 +02:00
+ public final float getLoadFactor() {
+ return this.loadFactor;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V>[] table = this.table;
+ for (;;) {
+ TableEntry<V> node = getAtIndexVolatile(table, hash & (table.length - 1));
+
2024-10-25 21:24:15 +02:00
+ if (node == null) {
+ // node == null
+ return node;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (node.resize) {
+ // noinspection unchecked
+ table = (TableEntry<V>[])node.getValuePlain();
+ continue;
+ }
+
2024-10-25 21:24:15 +02:00
+ for (; node != null; node = node.getNextVolatile()) {
+ if (node.key == key) {
+ return node;
+ }
+ }
2024-10-25 21:24:15 +02:00
+
+ // node == null
+ return node;
+ }
+ }
+
+ /**
2024-10-25 21:24:15 +02:00
+ * Returns the currently mapped value associated with the specified key, or {@code null} if there is none.
+ *
+ * @param key Specified key
+ */
2024-10-25 21:24:15 +02:00
+ public V get(final long key) {
+ final TableEntry<V> node = this.getNode(key);
+ return node == null ? null : node.getValueVolatile();
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ final V ret = node.getValueVolatile();
+ if (ret == null) {
+ // ret == null for nodes pre-allocated to compute() and friends
+ return defaultValue;
+ }
+
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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");
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
2024-10-25 21:24:15 +02:00
+
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * Returns the number of mappings in this map.
+ */
+ public int size() {
+ final long ret = this.size.sum();
+
2024-10-25 21:24:15 +02:00
+ if (ret < 0L) {
+ return 0;
+ }
2024-10-25 21:24:15 +02:00
+ if (ret > (long)Integer.MAX_VALUE) {
+ return Integer.MAX_VALUE;
+ }
+
2024-10-25 21:24:15 +02:00
+ return (int)ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * Returns whether this map has no mappings.
+ */
+ public boolean isEmpty() {
+ return this.size.sum() <= 0L;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * Adds count to size and checks threshold for resizing
+ */
+ protected final void addSize(final long count) {
+ this.size.add(count);
+
2024-10-25 21:24:15 +02:00
+ final int threshold = this.getThresholdAcquire();
+
2024-10-25 21:24:15 +02:00
+ if (threshold < 0L) {
+ // resizing or no resizing allowed, in either cases we do not need to do anything
+ return;
+ }
+
2024-10-25 21:24:15 +02:00
+ final long sum = this.size.sum();
+
2024-10-25 21:24:15 +02:00
+ if (sum < (long)threshold) {
+ return;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (threshold != this.compareExchangeThresholdVolatile(threshold, THRESHOLD_RESIZING)) {
+ // some other thread resized
+ return;
+ }
+
2024-10-25 21:24:15 +02:00
+ // create new table
+ this.resize(sum);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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;
+
2024-10-25 21:24:15 +02:00
+ // 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;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ // create new table data
+
2024-10-25 21:24:15 +02:00
+ // noinspection unchecked
+ final TableEntry<V>[] newTable = new TableEntry[capacity];
+ // noinspection unchecked
+ final TableEntry<V> resizeNode = new TableEntry<>(0L, (V)newTable, true);
+
2024-10-25 21:24:15 +02:00
+ // transfer nodes from old table
+
2024-10-25 21:24:15 +02:00
+ // does not need to be volatile read, just plain
+ final TableEntry<V>[] oldTable = this.table;
+
2024-10-25 21:24:15 +02:00
+ // 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
+
2024-10-25 21:24:15 +02:00
+ // 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
+
2024-10-25 21:24:15 +02:00
+ final int capOldShift = IntegerUtil.floorLog2(oldTable.length);
+ final int capDiffShift = IntegerUtil.floorLog2(capacity) - capOldShift;
+
+ if (capDiffShift == 0) {
+ throw new IllegalStateException("Resizing to same size");
+ }
+
2024-10-25 21:24:15 +02:00
+ // noinspection unchecked
+ final TableEntry<V>[] work = new TableEntry[1 << capDiffShift]; // typically, capDiffShift = 1
+
2024-10-25 21:24:15 +02:00
+ for (int i = 0, len = oldTable.length; i < len; ++i) {
+ TableEntry<V> binNode = getAtIndexVolatile(oldTable, i);
+
2024-10-25 21:24:15 +02:00
+ 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
+ }
+
2024-10-25 21:24:15 +02:00
+ // need write lock to block other writers
+ synchronized (binNode) {
+ if (binNode != (binNode = getAtIndexVolatile(oldTable, i))) {
+ continue;
+ }
+
2024-10-25 21:24:15 +02:00
+ // 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
+
2024-10-25 21:24:15 +02:00
+ 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 {
2024-10-25 21:24:15 +02:00
+ // reset for next usage
+ Arrays.fill(work, null);
+
2024-10-25 21:24:15 +02:00
+ for (TableEntry<V> curr = binNode; curr != null; curr = curr.getNextPlain()) {
+ final int newTableIdx = getHash(curr.key) & (capacity - 1);
+ final int workIdx = newTableIdx >>> capOldShift;
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<V> replace = new TableEntry<>(curr.key, curr.getValuePlain());
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ // calculate new threshold
+ final int newThreshold;
+ if (capacity == MAXIMUM_CAPACITY) {
+ newThreshold = THRESHOLD_NO_RESIZE;
+ } else {
+ newThreshold = getTargetThreshold(capacity, loadFactor);
+ }
+
2024-10-25 21:24:15 +02:00
+ this.table = newTable;
+ // finish resize operation by releasing hold on threshold
+ this.setThresholdVolatile(newThreshold);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * Subtracts count from size
+ */
+ protected final void subSize(final long count) {
+ this.size.add(-count);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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");
+
2024-10-25 21:24:15 +02:00
+ final int hash = getHash(key);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V>[] table = this.table;
+ table_loop:
+ for (;;) {
+ final int index = hash & (table.length - 1);
+
2024-10-25 21:24:15 +02:00
+ 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
+ }
+
2024-10-25 21:24:15 +02:00
+ if (node.resize) {
+ table = (TableEntry<V>[])node.getValuePlain();
+ continue table_loop;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ // 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));
+ }
+
2024-10-25 21:24:15 +02:00
+ this.addSize(1L);
+ return null;
+ }
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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");
+
2024-10-25 21:24:15 +02:00
+ final int hash = getHash(key);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V>[] table = this.table;
+ table_loop:
+ for (;;) {
+ final int index = hash & (table.length - 1);
+
2024-10-25 21:24:15 +02:00
+ 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
+ }
+
2024-10-25 21:24:15 +02:00
+ if (node.resize) {
+ // noinspection unchecked
+ table = (TableEntry<V>[])node.getValuePlain();
+ continue table_loop;
+ }
+
2024-10-25 21:24:15 +02:00
+ // 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
+ }
+
2024-10-25 21:24:15 +02:00
+ 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();
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ // 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));
+ }
+
2024-10-25 21:24:15 +02:00
+ this.addSize(1L);
+ return null;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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");
+
2024-10-25 21:24:15 +02:00
+ final int hash = getHash(key);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V>[] table = this.table;
+ table_loop:
+ for (;;) {
+ final int index = hash & (table.length - 1);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V> node = getAtIndexVolatile(table, index);
+ node_loop:
+ for (;;) {
+ if (node == null) {
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (node.resize) {
+ // noinspection unchecked
+ table = (TableEntry<V>[])node.getValuePlain();
+ continue table_loop;
+ }
+
2024-10-25 21:24:15 +02:00
+ synchronized (node) {
+ if (node != (node = getAtIndexVolatile(table, index))) {
+ continue node_loop;
+ }
+
2024-10-25 21:24:15 +02:00
+ // 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;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ return null;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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");
+
2024-10-25 21:24:15 +02:00
+ final int hash = getHash(key);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V>[] table = this.table;
+ table_loop:
+ for (;;) {
+ final int index = hash & (table.length - 1);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V> node = getAtIndexVolatile(table, index);
+ node_loop:
+ for (;;) {
+ if (node == null) {
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (node.resize) {
+ // noinspection unchecked
+ table = (TableEntry<V>[])node.getValuePlain();
+ continue table_loop;
+ }
+
2024-10-25 21:24:15 +02:00
+ synchronized (node) {
+ if (node != (node = getAtIndexVolatile(table, index))) {
+ continue node_loop;
+ }
+
2024-10-25 21:24:15 +02:00
+ // 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();
+
2024-10-25 21:24:15 +02:00
+ if (ret != expect) {
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ node.setValueVolatile(update);
+ return ret;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ return null;
+ }
+ }
+ }
+
+ /**
2024-10-25 21:24:15 +02:00
+ * 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.
+ */
2024-10-25 21:24:15 +02:00
+ public V remove(final long key) {
+ final int hash = getHash(key);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V>[] table = this.table;
+ table_loop:
+ for (;;) {
+ final int index = hash & (table.length - 1);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V> node = getAtIndexVolatile(table, index);
+ node_loop:
+ for (;;) {
+ if (node == null) {
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (node.resize) {
+ // noinspection unchecked
+ table = (TableEntry<V>[])node.getValuePlain();
+ continue table_loop;
+ }
+
2024-10-25 21:24:15 +02:00
+ boolean removed = false;
+ V ret = null;
+
2024-10-25 21:24:15 +02:00
+ 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());
+ }
+
2024-10-25 21:24:15 +02:00
+ break;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ if (removed) {
+ this.subSize(1L);
+ }
+
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+ }
+ }
+
+ /**
2024-10-25 21:24:15 +02:00
+ * 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.
+ */
2024-10-25 21:24:15 +02:00
+ public V remove(final long key, final V expect) {
+ final int hash = getHash(key);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V>[] table = this.table;
+ table_loop:
+ for (;;) {
+ final int index = hash & (table.length - 1);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V> node = getAtIndexVolatile(table, index);
+ node_loop:
+ for (;;) {
+ if (node == null) {
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (node.resize) {
+ // noinspection unchecked
+ table = (TableEntry<V>[])node.getValuePlain();
+ continue table_loop;
+ }
+
2024-10-25 21:24:15 +02:00
+ boolean removed = false;
+ V ret = null;
+
2024-10-25 21:24:15 +02:00
+ synchronized (node) {
+ if (node != (node = getAtIndexVolatile(table, index))) {
+ continue node_loop;
+ }
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V> prev = null;
+
2024-10-25 21:24:15 +02:00
+ // 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;
+
2024-10-25 21:24:15 +02:00
+ // 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;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ if (removed) {
+ this.subSize(1L);
+ }
+
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+ }
+ }
+
+ /**
2024-10-25 21:24:15 +02:00
+ * 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.
+ */
2024-10-25 21:24:15 +02:00
+ public V removeIf(final long key, final Predicate<? super V> predicate) {
+ Validate.notNull(predicate, "Predicate may not be null");
+
2024-10-25 21:24:15 +02:00
+ final int hash = getHash(key);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V>[] table = this.table;
+ table_loop:
+ for (;;) {
+ final int index = hash & (table.length - 1);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V> node = getAtIndexVolatile(table, index);
+ node_loop:
+ for (;;) {
+ if (node == null) {
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (node.resize) {
+ // noinspection unchecked
+ table = (TableEntry<V>[])node.getValuePlain();
+ continue table_loop;
+ }
+
2024-10-25 21:24:15 +02:00
+ boolean removed = false;
+ V ret = null;
+
2024-10-25 21:24:15 +02:00
+ synchronized (node) {
+ if (node != (node = getAtIndexVolatile(table, index))) {
+ continue node_loop;
+ }
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V> prev = null;
+
2024-10-25 21:24:15 +02:00
+ // 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;
+
2024-10-25 21:24:15 +02:00
+ // 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;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ if (removed) {
+ this.subSize(1L);
+ }
+
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+ }
+ }
+
+ /**
2024-10-25 21:24:15 +02:00
+ * See {@link java.util.concurrent.ConcurrentMap#compute(Object, BiFunction)}
+ * <p>
+ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}.
+ * </p>
+ */
2024-10-25 21:24:15 +02:00
+ public V compute(final long key, final BiLong1Function<? super V, ? extends V> function) {
+ final int hash = getHash(key);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V>[] table = this.table;
+ table_loop:
+ for (;;) {
+ final int index = hash & (table.length - 1);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V> node = getAtIndexVolatile(table, index);
+ node_loop:
+ for (;;) {
+ V ret = null;
+ if (node == null) {
+ final TableEntry<V> insert = new TableEntry<>(key, null);
+
2024-10-25 21:24:15 +02:00
+ boolean added = false;
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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
+ }
+
2024-10-25 21:24:15 +02:00
+ if (added) {
+ this.addSize(1L);
+ return ret;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ if (node.resize) {
+ // noinspection unchecked
+ table = (TableEntry<V>[])node.getValuePlain();
+ continue table_loop;
+ }
+
2024-10-25 21:24:15 +02:00
+ boolean removed = false;
+ boolean added = false;
+
2024-10-25 21:24:15 +02:00
+ 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();
+
2024-10-25 21:24:15 +02:00
+ final V computed = function.apply(key, old);
+
2024-10-25 21:24:15 +02:00
+ if (computed != null) {
+ node.setValueVolatile(computed);
+ return computed;
+ }
+
2024-10-25 21:24:15 +02:00
+ // 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());
+ }
+
2024-10-25 21:24:15 +02:00
+ removed = true;
+ break;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ if (removed) {
+ this.subSize(1L);
+ }
+ if (added) {
+ this.addSize(1L);
+ }
+
+ return ret;
+ }
+ }
+ }
+
+ /**
2024-10-25 21:24:15 +02:00
+ * See {@link java.util.concurrent.ConcurrentMap#computeIfAbsent(Object, Function)}
+ * <p>
+ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}.
+ * </p>
+ */
2024-10-25 21:24:15 +02:00
+ public V computeIfAbsent(final long key, final LongFunction<? extends V> function) {
+ final int hash = getHash(key);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V>[] table = this.table;
+ table_loop:
+ for (;;) {
+ final int index = hash & (table.length - 1);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V> node = getAtIndexVolatile(table, index);
+ node_loop:
+ for (;;) {
+ V ret = null;
+ if (node == null) {
+ final TableEntry<V> insert = new TableEntry<>(key, null);
+
2024-10-25 21:24:15 +02:00
+ boolean added = false;
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ // 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
+ }
+
2024-10-25 21:24:15 +02:00
+ boolean added = false;
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ if (added) {
+ this.addSize(1L);
+ }
+
+ return ret;
+ }
+ }
+ }
+
+ /**
2024-10-25 21:24:15 +02:00
+ * See {@link java.util.concurrent.ConcurrentMap#computeIfPresent(Object, BiFunction)}
+ * <p>
+ * This function is a "functional methods" as defined by {@link ConcurrentLong2ReferenceChainedHashTable}.
+ * </p>
+ */
2024-10-25 21:24:15 +02:00
+ public V computeIfPresent(final long key, final BiLong1Function<? super V, ? extends V> function) {
+ final int hash = getHash(key);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V>[] table = this.table;
+ table_loop:
+ for (;;) {
+ final int index = hash & (table.length - 1);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V> node = getAtIndexVolatile(table, index);
+ node_loop:
+ for (;;) {
+ if (node == null) {
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (node.resize) {
+ // noinspection unchecked
+ table = (TableEntry<V>[])node.getValuePlain();
+ continue table_loop;
+ }
+
2024-10-25 21:24:15 +02:00
+ boolean removed = false;
+
2024-10-25 21:24:15 +02:00
+ 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();
+
2024-10-25 21:24:15 +02:00
+ final V computed = function.apply(key, old);
+
2024-10-25 21:24:15 +02:00
+ if (computed != null) {
+ node.setValueVolatile(computed);
+ return computed;
+ }
+
2024-10-25 21:24:15 +02:00
+ // 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());
+ }
+
2024-10-25 21:24:15 +02:00
+ removed = true;
+ break;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ if (removed) {
+ this.subSize(1L);
+ }
+
2024-10-25 21:24:15 +02:00
+ return null;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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");
+
2024-10-25 21:24:15 +02:00
+ final int hash = getHash(key);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V>[] table = this.table;
+ table_loop:
+ for (;;) {
+ final int index = hash & (table.length - 1);
+
2024-10-25 21:24:15 +02:00
+ 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
+ }
+
2024-10-25 21:24:15 +02:00
+ if (node.resize) {
+ // noinspection unchecked
+ table = (TableEntry<V>[])node.getValuePlain();
+ continue table_loop;
+ }
+
2024-10-25 21:24:15 +02:00
+ boolean removed = false;
+ boolean added = false;
+ V ret = null;
+
2024-10-25 21:24:15 +02:00
+ 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();
+
2024-10-25 21:24:15 +02:00
+ final V computed = function.apply(old, def);
+
2024-10-25 21:24:15 +02:00
+ if (computed != null) {
+ node.setValueVolatile(computed);
+ return computed;
+ }
+
2024-10-25 21:24:15 +02:00
+ // 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());
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ if (removed) {
+ this.subSize(1L);
+ }
+ if (added) {
+ this.addSize(1L);
+ }
+
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+ }
+ }
+
+ /**
2024-10-25 21:24:15 +02:00
+ * 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>
+ */
2024-10-25 21:24:15 +02:00
+ 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);
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V> node;
+ while ((node = nodeIterator.findNext()) != null) {
+ this.remove(node.key);
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public final Iterator<TableEntry<V>> iterator() {
+ return this.entryIterator();
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected static final class EntryIterator<V> extends BaseIteratorImpl<V, TableEntry<V>> {
+
2024-10-25 21:24:15 +02:00
+ public EntryIterator(final ConcurrentLong2ReferenceChainedHashTable<V> map) {
+ super(map);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public TableEntry<V> next() throws NoSuchElementException {
+ return this.nextNode();
+ }
+
2024-10-25 21:24:15 +02:00
+ @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());
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ protected static final class KeyIterator<V> extends BaseIteratorImpl<V, Long> implements PrimitiveIterator.OfLong {
+
2024-10-25 21:24:15 +02:00
+ public KeyIterator(final ConcurrentLong2ReferenceChainedHashTable<V> map) {
+ super(map);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public Long next() throws NoSuchElementException {
+ return Long.valueOf(this.nextNode().key);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public long nextLong() {
+ return this.nextNode().key;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public void forEachRemaining(final Consumer<? super Long> action) {
+ Validate.notNull(action, "Action may not be null");
+
2024-10-25 21:24:15 +02:00
+ if (action instanceof LongConsumer longConsumer) {
+ this.forEachRemaining(longConsumer);
+ return;
+ }
+
2024-10-25 21:24:15 +02:00
+ while (this.hasNext()) {
+ action.accept(this.next());
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public void forEachRemaining(final LongConsumer action) {
+ Validate.notNull(action, "Action may not be null");
+ while (this.hasNext()) {
+ action.accept(this.nextLong());
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ protected static final class ValueIterator<V> extends BaseIteratorImpl<V, V> {
+
2024-10-25 21:24:15 +02:00
+ public ValueIterator(final ConcurrentLong2ReferenceChainedHashTable<V> map) {
+ super(map);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public V next() throws NoSuchElementException {
+ return this.nextNode().getValueVolatile();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public void forEachRemaining(final Consumer<? super V> action) {
+ Validate.notNull(action, "Action may not be null");
+ while (this.hasNext()) {
+ action.accept(this.next());
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ protected static abstract class BaseIteratorImpl<V, T> extends NodeIterator<V> implements Iterator<T> {
+
2024-10-25 21:24:15 +02:00
+ protected final ConcurrentLong2ReferenceChainedHashTable<V> map;
+ protected TableEntry<V> lastReturned;
+ protected TableEntry<V> nextToReturn;
+
2024-10-25 21:24:15 +02:00
+ protected BaseIteratorImpl(final ConcurrentLong2ReferenceChainedHashTable<V> map) {
+ super(map.table);
+ this.map = map;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public final boolean hasNext() {
+ if (this.nextToReturn != null) {
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ return (this.nextToReturn = this.findNext()) != null;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public final void remove() {
+ final TableEntry<V> lastReturned = this.lastReturned;
+ if (lastReturned == null) {
+ throw new NoSuchElementException();
+ }
2024-10-25 21:24:15 +02:00
+ this.lastReturned = null;
+ this.map.remove(lastReturned.key);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public abstract T next() throws NoSuchElementException;
+
2024-10-25 21:24:15 +02:00
+ // overwritten by subclasses to avoid indirection on hasNext() and next()
+ @Override
+ public abstract void forEachRemaining(final Consumer<? super T> action);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected static class NodeIterator<V> {
+
2024-10-25 21:24:15 +02:00
+ protected TableEntry<V>[] currentTable;
+ protected ResizeChain<V> resizeChain;
+ protected TableEntry<V> last;
+ protected int nextBin;
+ protected int increment;
+
2024-10-25 21:24:15 +02:00
+ protected NodeIterator(final TableEntry<V>[] baseTable) {
+ this.currentTable = baseTable;
+ this.increment = 1;
+ }
+
2024-10-25 21:24:15 +02:00
+ private TableEntry<V>[] pullResizeChain(final int index) {
+ final ResizeChain<V> resizeChain = this.resizeChain;
+ if (resizeChain == null) {
+ this.currentTable = null;
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ final ResizeChain<V> prevChain = resizeChain.prev;
+ this.resizeChain = prevChain;
+ if (prevChain == null) {
+ this.currentTable = null;
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<V>[] newTable = prevChain.table;
+
2024-10-25 21:24:15 +02:00
+ // 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);
+
2024-10-25 21:24:15 +02:00
+ // 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ // done with the upper table, so we can skip the resize node
+ newIdx += increment;
+
2024-10-25 21:24:15 +02:00
+ this.increment = increment;
+ this.nextBin = newIdx;
+ this.currentTable = newTable;
+
2024-10-25 21:24:15 +02:00
+ return newTable;
+ }
+
2024-10-25 21:24:15 +02:00
+ private TableEntry<V>[] pushResizeChain(final TableEntry<V>[] table, final TableEntry<V> entry) {
+ final ResizeChain<V> chain = this.resizeChain;
+
2024-10-25 21:24:15 +02:00
+ if (chain == null) {
+ // noinspection unchecked
+ final TableEntry<V>[] nextTable = (TableEntry<V>[])entry.getValuePlain();
+
2024-10-25 21:24:15 +02:00
+ final ResizeChain<V> oldChain = new ResizeChain<>(table, null, null);
+ final ResizeChain<V> currChain = new ResizeChain<>(nextTable, oldChain, null);
+ oldChain.next = currChain;
+
2024-10-25 21:24:15 +02:00
+ this.increment = table.length;
+ this.resizeChain = currChain;
+ this.currentTable = nextTable;
+
2024-10-25 21:24:15 +02:00
+ 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;
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ TableEntry<V>[] table = this.currentTable;
+
2024-10-25 21:24:15 +02:00
+ if (table == null) {
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<V> entry = getAtIndexVolatile(table, idx);
+ if (entry == null) {
+ idx += increment;
+ continue;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (entry.resize) {
+ // push onto resize chain
+ table = this.pushResizeChain(table, entry);
+ increment = this.increment;
+ continue;
+ }
+
2024-10-25 21:24:15 +02:00
+ this.last = entry;
+ this.nextBin = idx + increment;
+ if (entry.getValuePlain() != null) {
+ return entry;
+ } else {
+ // compute() node not yet available
+ break;
+ }
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ public static final class TableEntry<V> {
+
2024-10-25 21:24:15 +02:00
+ private static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class);
+
2024-10-25 21:24:15 +02:00
+ private final boolean resize;
+
2024-10-25 21:24:15 +02:00
+ private final long key;
+
2024-10-25 21:24:15 +02:00
+ private volatile V value;
+ private static final VarHandle VALUE_HANDLE = ConcurrentUtil.getVarHandle(TableEntry.class, "value", Object.class);
+
2024-10-25 21:24:15 +02:00
+ private V getValuePlain() {
+ //noinspection unchecked
+ return (V)VALUE_HANDLE.get(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ private V getValueAcquire() {
+ //noinspection unchecked
+ return (V)VALUE_HANDLE.getAcquire(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ private V getValueVolatile() {
+ //noinspection unchecked
+ return (V)VALUE_HANDLE.getVolatile(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ private void setValuePlain(final V value) {
+ VALUE_HANDLE.set(this, (Object)value);
+ }
+
2024-10-25 21:24:15 +02:00
+ private void setValueRelease(final V value) {
+ VALUE_HANDLE.setRelease(this, (Object)value);
+ }
+
2024-10-25 21:24:15 +02:00
+ private void setValueVolatile(final V value) {
+ VALUE_HANDLE.setVolatile(this, (Object)value);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ private TableEntry<V> getNextVolatile() {
+ //noinspection unchecked
+ return (TableEntry<V>)NEXT_HANDLE.getVolatile(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ private void setNextPlain(final TableEntry<V> next) {
+ NEXT_HANDLE.set(this, next);
+ }
+
2024-10-25 21:24:15 +02:00
+ private void setNextRelease(final TableEntry<V> next) {
+ NEXT_HANDLE.setRelease(this, next);
+ }
+
2024-10-25 21:24:15 +02:00
+ private void setNextVolatile(final TableEntry<V> next) {
+ NEXT_HANDLE.setVolatile(this, next);
+ }
+
2024-10-25 21:24:15 +02:00
+ public TableEntry(final long key, final V value) {
+ this.resize = false;
+ this.key = key;
+ this.setValuePlain(value);
+ }
+
2024-10-25 21:24:15 +02:00
+ public TableEntry(final long key, final V value, final boolean resize) {
+ this.resize = resize;
+ this.key = key;
+ this.setValuePlain(value);
+ }
+
2024-10-25 21:24:15 +02:00
+ public long getKey() {
+ return this.key;
+ }
+
2024-10-25 21:24:15 +02:00
+ public V getValue() {
+ return this.getValueVolatile();
+ }
+ }
2024-10-25 21:24:15 +02:00
+}
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;
+
2024-10-25 21:24:15 +02:00
+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;
+
2024-10-25 21:24:15 +02:00
+/**
+ * <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>> {
+
2024-10-25 21:24:15 +02:00
+ protected int size;
+
2024-10-25 21:24:15 +02:00
+ protected TableEntry<K, V>[] table;
+
2024-10-25 21:24:15 +02:00
+ protected final float loadFactor;
+
2024-10-25 21:24:15 +02:00
+ 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);
+
2024-10-25 21:24:15 +02:00
+ /* size */
+
2024-10-25 21:24:15 +02:00
+ protected final int getSizePlain() {
+ return (int)SIZE_HANDLE.get(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final int getSizeOpaque() {
+ return (int)SIZE_HANDLE.getOpaque(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final int getSizeAcquire() {
+ return (int)SIZE_HANDLE.getAcquire(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setSizePlain(final int value) {
+ SIZE_HANDLE.set(this, value);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setSizeOpaque(final int value) {
+ SIZE_HANDLE.setOpaque(this, value);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setSizeRelease(final int value) {
+ SIZE_HANDLE.setRelease(this, value);
+ }
+
2024-10-25 21:24:15 +02:00
+ /* table */
+
2024-10-25 21:24:15 +02:00
+ protected final TableEntry<K, V>[] getTablePlain() {
+ //noinspection unchecked
+ return (TableEntry<K, V>[])TABLE_HANDLE.get(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final TableEntry<K, V>[] getTableAcquire() {
+ //noinspection unchecked
+ return (TableEntry<K, V>[])TABLE_HANDLE.getAcquire(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setTablePlain(final TableEntry<K, V>[] table) {
+ TABLE_HANDLE.set(this, table);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setTableRelease(final TableEntry<K, V>[] table) {
+ TABLE_HANDLE.setRelease(this, table);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+
2024-10-25 21:24:15 +02:00
+ /**
+ * Constructs this map with a capacity of {@code 16} and load factor of {@code 0.75f}.
+ */
+ public SWMRHashTable() {
+ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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);
+
2024-10-25 21:24:15 +02:00
+ if (loadFactor <= 0.0 || !Float.isFinite(loadFactor)) {
+ throw new IllegalArgumentException("Invalid load factor: " + loadFactor);
+ }
+
2024-10-25 21:24:15 +02:00
+ //noinspection unchecked
+ final TableEntry<K, V>[] table = new TableEntry[tableSize];
+ this.setTablePlain(table);
+
2024-10-25 21:24:15 +02:00
+ if (tableSize == MAXIMUM_CAPACITY) {
+ this.threshold = -1;
+ } else {
+ this.threshold = getTargetCapacity(tableSize, loadFactor);
+ }
+
2024-10-25 21:24:15 +02:00
+ this.loadFactor = loadFactor;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ public final float getLoadFactor() {
+ return this.loadFactor;
+ }
+
2024-10-25 21:24:15 +02:00
+ protected static int getCapacityFor(final int capacity) {
+ if (capacity <= 0) {
+ throw new IllegalArgumentException("Invalid capacity: " + capacity);
+ }
2024-10-25 21:24:15 +02:00
+ if (capacity >= MAXIMUM_CAPACITY) {
+ return MAXIMUM_CAPACITY;
+ }
2024-10-25 21:24:15 +02:00
+ return IntegerUtil.roundCeilLog2(capacity);
+ }
+
2024-10-25 21:24:15 +02:00
+ /** 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();
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final TableEntry<K, V> getEntryForPlain(final K key) {
+ final int hash = SWMRHashTable.getHash(key);
+ final TableEntry<K, V>[] table = this.getTablePlain();
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ /* MT-Safe */
+
2024-10-25 21:24:15 +02:00
+ /** must be deterministic given a key */
+ private static int getHash(final Object key) {
+ int hash = key == null ? 0 : key.hashCode();
+ return HashUtil.mix(hash);
+ }
+
2024-10-25 21:24:15 +02:00
+ // 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ return (int)ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
2024-10-25 21:24:15 +02:00
+ /* Make no attempt to deal with concurrent modifications */
+ if (!(obj instanceof Map<?, ?> other)) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (this.size() != other.size()) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<K, V>[] table = this.getTableAcquire();
+
2024-10-25 21:24:15 +02:00
+ 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();
+
2024-10-25 21:24:15 +02:00
+ final Object otherValue = other.get(curr.key);
+ if (otherValue == null || (value != otherValue && value.equals(otherValue))) {
+ return false;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ /* Make no attempt to deal with concurrent modifications */
+ int hash = 0;
+ final TableEntry<K, V>[] table = this.getTableAcquire();
+
2024-10-25 21:24:15 +02:00
+ 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();
+ }
+ }
2024-10-25 21:24:15 +02:00
+
+ return hash;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder(64);
+ builder.append("SWMRHashTable:{");
+
2024-10-25 21:24:15 +02:00
+ this.forEach((final K key, final V value) -> {
+ builder.append("{key: \"").append(key).append("\", value: \"").append(value).append("\"}");
+ });
+
2024-10-25 21:24:15 +02:00
+ return builder.append('}').toString();
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SWMRHashTable<K, V> clone() {
+ return new SWMRHashTable<>(this.getTableAcquire().length, this.loadFactor, this);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Iterator<Map.Entry<K, V>> iterator() {
+ return new EntryIterator<>(this.getTableAcquire(), this);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void forEach(final Consumer<? super Map.Entry<K, V>> action) {
+ Validate.notNull(action, "Null action");
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void forEach(final BiConsumer<? super K, ? super V> action) {
+ Validate.notNull(action, "Null action");
+
2024-10-25 21:24:15 +02:00
+ 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();
+
2024-10-25 21:24:15 +02:00
+ action.accept(curr.key, value);
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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");
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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");
+
2024-10-25 21:24:15 +02:00
+ 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();
+
2024-10-25 21:24:15 +02:00
+ action.accept(value);
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public V get(final Object key) {
+ Validate.notNull(key, "Null key");
+
2024-10-25 21:24:15 +02:00
+ //noinspection unchecked
+ final TableEntry<K, V> entry = this.getEntryForOpaque((K)key);
+ return entry == null ? null : entry.getValueAcquire();
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean containsKey(final Object key) {
+ Validate.notNull(key, "Null key");
+
2024-10-25 21:24:15 +02:00
+ // note: we need to use getValueAcquire, so that the reads from this map are ordered by acquire semantics
+ return this.get(key) != null;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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");
+
2024-10-25 21:24:15 +02:00
+ //noinspection unchecked
+ final TableEntry<K, V> entry = this.getEntryForOpaque((K)key);
+
2024-10-25 21:24:15 +02:00
+ if (entry == null) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ final V entryVal = entry.getValueAcquire();
+ return entryVal == value || entryVal.equals(value);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean containsValue(final Object value) {
+ Validate.notNull(value, "Null value");
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public V getOrDefault(final Object key, final V defaultValue) {
+ Validate.notNull(key, "Null key");
+
2024-10-25 21:24:15 +02:00
+ //noinspection unchecked
+ final TableEntry<K, V> entry = this.getEntryForOpaque((K)key);
+
+ return entry == null ? defaultValue : entry.getValueAcquire();
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int size() {
+ return this.getSizeAcquire();
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isEmpty() {
+ return this.getSizeAcquire() == 0;
+ }
+
2024-10-25 21:24:15 +02:00
+ protected KeySet<K, V> keyset;
+ protected ValueCollection<K, V> values;
+ protected EntrySet<K, V> entrySet;
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public Set<K> keySet() {
+ return this.keyset == null ? this.keyset = new KeySet<>(this) : this.keyset;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public Collection<V> values() {
+ return this.values == null ? this.values = new ValueCollection<>(this) : this.values;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public Set<Map.Entry<K, V>> entrySet() {
+ return this.entrySet == null ? this.entrySet = new EntrySet<>(this) : this.entrySet;
+ }
+
2024-10-25 21:24:15 +02:00
+ /* Non-MT-Safe */
+
2024-10-25 21:24:15 +02:00
+ protected int threshold;
+
2024-10-25 21:24:15 +02:00
+ protected final void checkResize(final int minCapacity) {
+ if (minCapacity <= this.threshold || this.threshold < 0) {
+ return;
+ }
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<K, V>[] table = this.getTablePlain();
+ int newCapacity = minCapacity >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : IntegerUtil.roundCeilLog2(minCapacity);
+ if (newCapacity < 0) {
+ newCapacity = MAXIMUM_CAPACITY;
+ }
2024-10-25 21:24:15 +02:00
+ if (newCapacity <= table.length) {
+ if (newCapacity == MAXIMUM_CAPACITY) {
+ return;
+ }
+ newCapacity = table.length << 1;
+ }
+
2024-10-25 21:24:15 +02:00
+ //noinspection unchecked
+ final TableEntry<K, V>[] newTable = new TableEntry[newCapacity];
+ final int indexMask = newCapacity - 1;
+
2024-10-25 21:24:15 +02:00
+ 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;
+
2024-10-25 21:24:15 +02:00
+ /* 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());
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<K, V> prev = newTable[index];
+
2024-10-25 21:24:15 +02:00
+ newTable[index] = insert;
+ insert.setNextPlain(prev);
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ if (newCapacity == MAXIMUM_CAPACITY) {
+ this.threshold = -1; /* No more resizing */
+ } else {
+ this.threshold = getTargetCapacity(newCapacity, this.loadFactor);
+ }
2024-10-25 21:24:15 +02:00
+ this.setTableRelease(newTable); /* use release to publish entries in table */
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final int addToSize(final int num) {
+ final int newSize = this.getSizePlain() + num;
+
2024-10-25 21:24:15 +02:00
+ this.setSizeOpaque(newSize);
+ this.checkResize(newSize);
+
2024-10-25 21:24:15 +02:00
+ return newSize;
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final int removeFromSize(final int num) {
+ final int newSize = this.getSizePlain() - num;
+
2024-10-25 21:24:15 +02:00
+ this.setSizeOpaque(newSize);
+
2024-10-25 21:24:15 +02:00
+ return newSize;
+ }
+
2024-10-25 21:24:15 +02:00
+ /* Cannot be used to perform downsizing */
+ protected final int removeFromSizePlain(final int num) {
+ final int newSize = this.getSizePlain() - num;
+
2024-10-25 21:24:15 +02:00
+ this.setSizePlain(newSize);
+
2024-10-25 21:24:15 +02:00
+ return newSize;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ for (TableEntry<K, V> curr = head;;) {
+ if (curr.hash == hash && (key == curr.key || curr.key.equals(key))) {
+ if (onlyIfAbsent) {
+ return curr.getValuePlain();
+ }
+
2024-10-25 21:24:15 +02:00
+ final V currVal = curr.getValuePlain();
+ curr.setValueRelease(value);
+ return currVal;
+ }
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<K, V> next = curr.getNextPlain();
+ if (next != null) {
+ curr = next;
+ continue;
+ }
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<K, V> insert = new TableEntry<>(hash, key, value);
+
2024-10-25 21:24:15 +02:00
+ curr.setNextRelease(insert);
+ this.addToSize(1);
+ return null;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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");
+
2024-10-25 21:24:15 +02:00
+ int removed = 0;
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<K, V>[] table = this.getTablePlain();
+
2024-10-25 21:24:15 +02:00
+ bin_iteration_loop:
+ for (int i = 0, len = table.length; i < len; ++i) {
+ TableEntry<K, V> curr = table[i];
+ if (curr == null) {
+ continue;
+ }
+
2024-10-25 21:24:15 +02:00
+ /* Handle bin nodes first */
+ while (predicate.test(curr.key, curr.getValuePlain())) {
+ ++removed;
+ this.removeFromSizePlain(1); /* required in case predicate throws an exception */
+
2024-10-25 21:24:15 +02:00
+ setAtIndexRelease(table, i, curr = curr.getNextPlain());
+
2024-10-25 21:24:15 +02:00
+ if (curr == null) {
+ continue bin_iteration_loop;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ TableEntry<K, V> prev;
+
2024-10-25 21:24:15 +02:00
+ /* curr at this point is the bin node */
+
2024-10-25 21:24:15 +02:00
+ 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 */
+
2024-10-25 21:24:15 +02:00
+ prev.setNextRelease(curr = curr.getNextPlain());
+ } else {
+ prev = curr;
+ curr = curr.getNextPlain();
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ return removed;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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");
+
2024-10-25 21:24:15 +02:00
+ int removed = 0;
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<K, V>[] table = this.getTablePlain();
+
2024-10-25 21:24:15 +02:00
+ bin_iteration_loop:
+ for (int i = 0, len = table.length; i < len; ++i) {
+ TableEntry<K, V> curr = table[i];
+ if (curr == null) {
+ continue;
+ }
+
2024-10-25 21:24:15 +02:00
+ /* Handle bin nodes first */
+ while (predicate.test(curr)) {
+ ++removed;
+ this.removeFromSizePlain(1); /* required in case predicate throws an exception */
+
2024-10-25 21:24:15 +02:00
+ setAtIndexRelease(table, i, curr = curr.getNextPlain());
+
2024-10-25 21:24:15 +02:00
+ if (curr == null) {
+ continue bin_iteration_loop;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ TableEntry<K, V> prev;
+
2024-10-25 21:24:15 +02:00
+ /* curr at this point is the bin node */
+
2024-10-25 21:24:15 +02:00
+ 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();
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ return removed;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public V put(final K key, final V value) {
+ Validate.notNull(key, "Null key");
+ Validate.notNull(value, "Null value");
+
2024-10-25 21:24:15 +02:00
+ return this.put(key, value, false);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public V putIfAbsent(final K key, final V value) {
+ Validate.notNull(key, "Null key");
+ Validate.notNull(value, "Null value");
+
2024-10-25 21:24:15 +02:00
+ return this.put(key, value, true);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean remove(final Object key, final Object value) {
+ Validate.notNull(key, "Null key");
+ Validate.notNull(value, "Null value");
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<K, V>[] table = this.getTablePlain();
+ final int hash = SWMRHashTable.getHash(key);
+ final int index = hash & (table.length - 1);
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<K, V> head = table[index];
+ if (head == null) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (head.hash == hash && (head.key == key || head.key.equals(key))) {
+ final V currVal = head.getValuePlain();
+
2024-10-25 21:24:15 +02:00
+ if (currVal != value && !currVal.equals(value)) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ setAtIndexRelease(table, index, head.getNextPlain());
+ this.removeFromSize(1);
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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();
+
2024-10-25 21:24:15 +02:00
+ if (currVal != value && !currVal.equals(value)) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ prev.setNextRelease(curr.getNextPlain());
+ this.removeFromSize(1);
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+ }
+
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final V remove(final Object key, final int hash) {
+ final TableEntry<K, V>[] table = this.getTablePlain();
+ final int index = (table.length - 1) & hash;
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<K, V> head = table[index];
+ if (head == null) {
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (hash == head.hash && (head.key == key || head.key.equals(key))) {
+ setAtIndexRelease(table, index, head.getNextPlain());
+ this.removeFromSize(1);
+
2024-10-25 21:24:15 +02:00
+ return head.getValuePlain();
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+
2024-10-25 21:24:15 +02:00
+ return curr.getValuePlain();
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ return null;
+ }
+
+ /**
2024-10-25 21:24:15 +02:00
+ * {@inheritDoc}
+ */
2024-10-25 21:24:15 +02:00
+ @Override
+ public V remove(final Object key) {
+ Validate.notNull(key, "Null key");
+
+ return this.remove(key, SWMRHashTable.getHash(key));
+ }
+
+ /**
2024-10-25 21:24:15 +02:00
+ * {@inheritDoc}
+ */
2024-10-25 21:24:15 +02:00
+ @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");
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<K, V> entry = this.getEntryForPlain(key);
+ if (entry == null) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ final V currValue = entry.getValuePlain();
+ if (currValue == oldValue || currValue.equals(oldValue)) {
+ entry.setValueRelease(newValue);
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ return false;
+ }
+
+ /**
2024-10-25 21:24:15 +02:00
+ * {@inheritDoc}
+ */
2024-10-25 21:24:15 +02:00
+ @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;
+ }
+
+ /**
2024-10-25 21:24:15 +02:00
+ * {@inheritDoc}
+ */
2024-10-25 21:24:15 +02:00
+ @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);
+ }
+ }
+ }
+
+ /**
2024-10-25 21:24:15 +02:00
+ * {@inheritDoc}
+ */
2024-10-25 21:24:15 +02:00
+ @Override
+ public void putAll(final Map<? extends K, ? extends V> map) {
+ Validate.notNull(map, "Null map");
+
2024-10-25 21:24:15 +02:00
+ final int size = map.size();
+ this.checkResize(Math.max(this.getSizePlain() + size/2, size)); /* preemptively resize */
+ map.forEach(this::put);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@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);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@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");
+
2024-10-25 21:24:15 +02:00
+ final int hash = SWMRHashTable.getHash(key);
+ final TableEntry<K, V>[] table = this.getTablePlain();
+ final int index = hash & (table.length - 1);
+
2024-10-25 21:24:15 +02:00
+ for (TableEntry<K, V> curr = table[index], prev = null;;prev = curr, curr = curr.getNextPlain()) {
+ if (curr == null) {
+ final V newVal = remappingFunction.apply(key ,null);
+
2024-10-25 21:24:15 +02:00
+ if (newVal == null) {
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<K, V> insert = new TableEntry<>(hash, key, newVal);
+ if (prev == null) {
+ setAtIndexRelease(table, index, insert);
+ } else {
+ prev.setNextRelease(insert);
+ }
+
2024-10-25 21:24:15 +02:00
+ this.addToSize(1);
+
2024-10-25 21:24:15 +02:00
+ return newVal;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (curr.hash == hash && (curr.key == key || curr.key.equals(key))) {
+ final V newVal = remappingFunction.apply(key, curr.getValuePlain());
+
2024-10-25 21:24:15 +02:00
+ if (newVal != null) {
+ curr.setValueRelease(newVal);
+ return newVal;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (prev == null) {
+ setAtIndexRelease(table, index, curr.getNextPlain());
+ } else {
+ prev.setNextRelease(curr.getNextPlain());
+ }
+
2024-10-25 21:24:15 +02:00
+ this.removeFromSize(1);
+
2024-10-25 21:24:15 +02:00
+ return null;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
2024-10-25 21:24:15 +02:00
+ 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");
+
2024-10-25 21:24:15 +02:00
+ final int hash = SWMRHashTable.getHash(key);
+ final TableEntry<K, V>[] table = this.getTablePlain();
+ final int index = hash & (table.length - 1);
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ final V newVal = remappingFunction.apply(key, curr.getValuePlain());
+ if (newVal != null) {
+ curr.setValueRelease(newVal);
+ return newVal;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (prev == null) {
+ setAtIndexRelease(table, index, curr.getNextPlain());
+ } else {
+ prev.setNextRelease(curr.getNextPlain());
+ }
+
2024-10-25 21:24:15 +02:00
+ this.removeFromSize(1);
+
2024-10-25 21:24:15 +02:00
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
2024-10-25 21:24:15 +02:00
+ public V computeIfAbsent(final K key, final Function<? super K, ? extends V> mappingFunction) {
+ Validate.notNull(key, "Null key");
+ Validate.notNull(mappingFunction, "Null mappingFunction");
+
2024-10-25 21:24:15 +02:00
+ final int hash = SWMRHashTable.getHash(key);
+ final TableEntry<K, V>[] table = this.getTablePlain();
+ final int index = hash & (table.length - 1);
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ final V newVal = mappingFunction.apply(key);
+
2024-10-25 21:24:15 +02:00
+ if (newVal == null) {
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<K, V> insert = new TableEntry<>(hash, key, newVal);
+ if (prev == null) {
+ setAtIndexRelease(table, index, insert);
+ } else {
+ prev.setNextRelease(insert);
+ }
2024-10-25 21:24:15 +02:00
+
+ this.addToSize(1);
+
+ return newVal;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
2024-10-25 21:24:15 +02:00
+ @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");
+
2024-10-25 21:24:15 +02:00
+ final int hash = SWMRHashTable.getHash(key);
+ final TableEntry<K, V>[] table = this.getTablePlain();
+ final int index = hash & (table.length - 1);
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ this.addToSize(1);
+
2024-10-25 21:24:15 +02:00
+ return value;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (curr.hash == hash && (curr.key == key || curr.key.equals(key))) {
+ final V newVal = remappingFunction.apply(curr.getValuePlain(), value);
+
2024-10-25 21:24:15 +02:00
+ if (newVal != null) {
+ curr.setValueRelease(newVal);
+ return newVal;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (prev == null) {
+ setAtIndexRelease(table, index, curr.getNextPlain());
+ } else {
+ prev.setNextRelease(curr.getNextPlain());
+ }
+
+ this.removeFromSize(1);
+
+ return null;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ protected static final class TableEntry<K, V> implements Map.Entry<K, V> {
+
2024-10-25 21:24:15 +02:00
+ protected static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class);
+
2024-10-25 21:24:15 +02:00
+ protected final int hash;
+ protected final K key;
+ protected V value;
+
2024-10-25 21:24:15 +02:00
+ protected TableEntry<K, V> next;
+
2024-10-25 21:24:15 +02:00
+ 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);
+
2024-10-25 21:24:15 +02:00
+ /* value */
+
2024-10-25 21:24:15 +02:00
+ protected final V getValuePlain() {
+ //noinspection unchecked
+ return (V)VALUE_HANDLE.get(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final V getValueAcquire() {
+ //noinspection unchecked
+ return (V)VALUE_HANDLE.getAcquire(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setValueRelease(final V to) {
+ VALUE_HANDLE.setRelease(this, to);
+ }
+
2024-10-25 21:24:15 +02:00
+ /* next */
+
+ protected final TableEntry<K, V> getNextPlain() {
+ //noinspection unchecked
+ return (TableEntry<K, V>)NEXT_HANDLE.get(this);
+ }
2024-10-25 21:24:15 +02:00
+
+ protected final TableEntry<K, V> getNextOpaque() {
+ //noinspection unchecked
+ return (TableEntry<K, V>)NEXT_HANDLE.getOpaque(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setNextPlain(final TableEntry<K, V> next) {
+ NEXT_HANDLE.set(this, next);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setNextRelease(final TableEntry<K, V> next) {
+ NEXT_HANDLE.setRelease(this, next);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected TableEntry(final int hash, final K key, final V value) {
+ this.hash = hash;
+ this.key = key;
+ this.value = value;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public K getKey() {
+ return this.key;
+ }
+
2024-10-25 21:24:15 +02:00
+ @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());
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@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));
+ }
+ }
+
+
2024-10-25 21:24:15 +02:00
+ protected static abstract class TableEntryIterator<K, V, T> implements Iterator<T> {
+
2024-10-25 21:24:15 +02:00
+ protected final TableEntry<K, V>[] table;
+ protected final SWMRHashTable<K, V> map;
+
2024-10-25 21:24:15 +02:00
+ /* bin which our current element resides on */
+ protected int tableIndex;
+
2024-10-25 21:24:15 +02:00
+ 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 */
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean hasNext() {
+ return this.nextEntry != null;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ this.currEntry = curr;
+
+ // set up nextEntry
+
+ // find next in chain
+ TableEntry<K, V> next = curr.getNextOpaque();
+
+ if (next != null) {
2024-10-25 21:24:15 +02:00
+ this.nextEntry = next;
+ return curr;
+ }
+
2024-10-25 21:24:15 +02:00
+ // 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;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ this.nextEntry = null;
+ this.tableIndex = tableIndex;
+ return curr;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public void remove() {
+ final TableEntry<K, V> curr = this.currEntry;
+ if (curr == null) {
+ throw new IllegalStateException();
+ }
+
2024-10-25 21:24:15 +02:00
+ this.map.remove(curr.key, curr.hash);
+
2024-10-25 21:24:15 +02:00
+ this.currEntry = null;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ protected static final class ValueIterator<K, V> extends TableEntryIterator<K, V, V> {
+
2024-10-25 21:24:15 +02:00
+ protected ValueIterator(final TableEntry<K, V>[] table, final SWMRHashTable<K, V> map) {
+ super(table, map);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public V next() {
+ final TableEntry<K, V> entry = this.advanceEntry();
+
2024-10-25 21:24:15 +02:00
+ if (entry == null) {
+ throw new NoSuchElementException();
+ }
+
+ return entry.getValueAcquire();
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ protected static final class KeyIterator<K, V> extends TableEntryIterator<K, V, K> {
+
2024-10-25 21:24:15 +02:00
+ protected KeyIterator(final TableEntry<K, V>[] table, final SWMRHashTable<K, V> map) {
+ super(table, map);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public K next() {
+ final TableEntry<K, V> curr = this.advanceEntry();
+
2024-10-25 21:24:15 +02:00
+ if (curr == null) {
+ throw new NoSuchElementException();
+ }
+
2024-10-25 21:24:15 +02:00
+ return curr.key;
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ protected static final class EntryIterator<K, V> extends TableEntryIterator<K, V, Map.Entry<K, V>> {
+
2024-10-25 21:24:15 +02:00
+ protected EntryIterator(final TableEntry<K, V>[] table, final SWMRHashTable<K, V> map) {
+ super(table, map);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public Map.Entry<K, V> next() {
+ final TableEntry<K, V> curr = this.advanceEntry();
+
2024-10-25 21:24:15 +02:00
+ if (curr == null) {
+ throw new NoSuchElementException();
+ }
+
2024-10-25 21:24:15 +02:00
+ return curr;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ protected static abstract class ViewCollection<K, V, T> implements Collection<T> {
+
2024-10-25 21:24:15 +02:00
+ protected final SWMRHashTable<K, V> map;
+
2024-10-25 21:24:15 +02:00
+ protected ViewCollection(final SWMRHashTable<K, V> map) {
+ this.map = map;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean add(final T element) {
+ throw new UnsupportedOperationException();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean addAll(final Collection<? extends T> collections) {
+ throw new UnsupportedOperationException();
+ }
+
2024-10-25 21:24:15 +02:00
+ @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;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public int size() {
+ return this.map.size();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean isEmpty() {
+ return this.size() == 0;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public void clear() {
+ this.map.clear();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean containsAll(final Collection<?> collection) {
+ Validate.notNull(collection, "Null collection");
+
2024-10-25 21:24:15 +02:00
+ for (final Object element : collection) {
+ if (!this.contains(element)) {
+ return false;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public Object[] toArray() {
+ final List<T> list = new ArrayList<>(this.size());
+
2024-10-25 21:24:15 +02:00
+ this.forEach(list::add);
+
+ return list.toArray();
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public <E> E[] toArray(final E[] array) {
+ final List<T> list = new ArrayList<>(this.size());
+
2024-10-25 21:24:15 +02:00
+ this.forEach(list::add);
+
2024-10-25 21:24:15 +02:00
+ return list.toArray(array);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public <E> E[] toArray(final IntFunction<E[]> generator) {
+ final List<T> list = new ArrayList<>(this.size());
+
2024-10-25 21:24:15 +02:00
+ this.forEach(list::add);
+
2024-10-25 21:24:15 +02:00
+ return list.toArray(generator);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ for (final T element : this) {
+ hash += element == null ? 0 : element.hashCode();
+ }
+ return hash;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public Spliterator<T> spliterator() { // TODO implement
+ return Spliterators.spliterator(this, Spliterator.NONNULL);
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ protected static abstract class ViewSet<K, V, T> extends ViewCollection<K, V, T> implements Set<T> {
+
2024-10-25 21:24:15 +02:00
+ protected ViewSet(final SWMRHashTable<K, V> map) {
+ super(map);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (!(obj instanceof Set)) {
+ return false;
+ }
+
+ final Set<?> other = (Set<?>)obj;
+ if (other.size() != this.size()) {
+ return false;
+ }
+
+ return this.containsAll(other);
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ protected static final class EntrySet<K, V> extends ViewSet<K, V, Map.Entry<K, V>> implements Set<Map.Entry<K, V>> {
+
2024-10-25 21:24:15 +02:00
+ protected EntrySet(final SWMRHashTable<K, V> map) {
+ super(map);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean remove(final Object object) {
+ if (!(object instanceof Map.Entry<?, ?> entry)) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ final Object key;
+ final Object value;
+
2024-10-25 21:24:15 +02:00
+ try {
+ key = entry.getKey();
+ value = entry.getValue();
+ } catch (final IllegalStateException ex) {
+ return false;
+ }
+
+ return this.map.remove(key, value);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean removeIf(final Predicate<? super Map.Entry<K, V>> filter) {
+ Validate.notNull(filter, "Null filter");
+
+ return this.map.removeEntryIf(filter) != 0;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean retainAll(final Collection<?> collection) {
+ Validate.notNull(collection, "Null collection");
+
2024-10-25 21:24:15 +02:00
+ return this.map.removeEntryIf((final Map.Entry<K, V> entry) -> {
+ return !collection.contains(entry);
+ }) != 0;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public Iterator<Map.Entry<K, V>> iterator() {
+ return new EntryIterator<>(this.map.getTableAcquire(), this.map);
+ }
2024-10-25 21:24:15 +02:00
+
+ @Override
+ public void forEach(final Consumer<? super Map.Entry<K, V>> action) {
+ this.map.forEach(action);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean contains(final Object object) {
+ if (!(object instanceof Map.Entry<?, ?> entry)) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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");
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ protected static final class KeySet<K, V> extends ViewSet<K, V, K> {
+
2024-10-25 21:24:15 +02:00
+ protected KeySet(final SWMRHashTable<K, V> map) {
+ super(map);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public Iterator<K> iterator() {
+ return new KeyIterator<>(this.map.getTableAcquire(), this.map);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public void forEach(final Consumer<? super K> action) {
+ Validate.notNull(action, "Null action");
+
2024-10-25 21:24:15 +02:00
+ this.map.forEachKey(action);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean contains(final Object key) {
+ Validate.notNull(key, "Null key");
+
2024-10-25 21:24:15 +02:00
+ return this.map.containsKey(key);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean remove(final Object key) {
+ Validate.notNull(key, "Null key");
+
2024-10-25 21:24:15 +02:00
+ return this.map.remove(key) != null;
+ }
+
2024-10-25 21:24:15 +02:00
+ @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;
+ }
2024-10-25 21:24:15 +02:00
+
+ @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;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public String toString() {
+ return CollectionUtil.toString(this, "SWMRHashTableKeySet");
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ protected static final class ValueCollection<K, V> extends ViewSet<K, V, V> implements Collection<V> {
+
2024-10-25 21:24:15 +02:00
+ protected ValueCollection(final SWMRHashTable<K, V> map) {
+ super(map);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public Iterator<V> iterator() {
+ return new ValueIterator<>(this.map.getTableAcquire(), this.map);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public void forEach(final Consumer<? super V> action) {
+ Validate.notNull(action, "Null action");
+
2024-10-25 21:24:15 +02:00
+ this.map.forEachValue(action);
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean contains(final Object object) {
+ Validate.notNull(object, "Null object");
+
2024-10-25 21:24:15 +02:00
+ return this.map.containsValue(object);
+ }
+
2024-10-25 21:24:15 +02:00
+ @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;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean removeIf(final Predicate<? super V> filter) {
+ Validate.notNull(filter, "Null filter");
+
2024-10-25 21:24:15 +02:00
+ return this.map.removeIf((final K key, final V value) -> {
+ return filter.test(value);
+ }) != 0;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean retainAll(final Collection<?> collection) {
+ Validate.notNull(collection, "Null collection");
+
2024-10-25 21:24:15 +02:00
+ return this.map.removeIf((final K key, final V value) -> {
+ return !collection.contains(value);
+ }) != 0;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public String toString() {
+ return CollectionUtil.toString(this, "SWMRHashTableValues");
+ }
+ }
2024-10-25 21:24:15 +02:00
+}
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;
+
2024-10-25 21:24:15 +02:00
+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;
+
2024-10-25 21:24:15 +02:00
+// trimmed down version of SWMRHashTable
+public class SWMRLong2ObjectHashTable<V> {
+
2024-10-25 21:24:15 +02:00
+ protected int size;
+
2024-10-25 21:24:15 +02:00
+ protected TableEntry<V>[] table;
+
2024-10-25 21:24:15 +02:00
+ protected final float loadFactor;
+
2024-10-25 21:24:15 +02:00
+ 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);
+
2024-10-25 21:24:15 +02:00
+ /* size */
+
2024-10-25 21:24:15 +02:00
+ protected final int getSizePlain() {
+ return (int)SIZE_HANDLE.get(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final int getSizeOpaque() {
+ return (int)SIZE_HANDLE.getOpaque(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final int getSizeAcquire() {
+ return (int)SIZE_HANDLE.getAcquire(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setSizePlain(final int value) {
+ SIZE_HANDLE.set(this, value);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setSizeOpaque(final int value) {
+ SIZE_HANDLE.setOpaque(this, value);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setSizeRelease(final int value) {
+ SIZE_HANDLE.setRelease(this, value);
+ }
+
2024-10-25 21:24:15 +02:00
+ /* table */
+
2024-10-25 21:24:15 +02:00
+ protected final TableEntry<V>[] getTablePlain() {
+ //noinspection unchecked
+ return (TableEntry<V>[])TABLE_HANDLE.get(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final TableEntry<V>[] getTableAcquire() {
+ //noinspection unchecked
+ return (TableEntry<V>[])TABLE_HANDLE.getAcquire(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setTablePlain(final TableEntry<V>[] table) {
+ TABLE_HANDLE.set(this, table);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setTableRelease(final TableEntry<V>[] table) {
+ TABLE_HANDLE.setRelease(this, table);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+
2024-10-25 21:24:15 +02:00
+ /**
+ * Constructs this map with a capacity of {@code 16} and load factor of {@code 0.75f}.
+ */
+ public SWMRLong2ObjectHashTable() {
+ this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ //noinspection unchecked
+ final TableEntry<V>[] table = new TableEntry[tableSize];
+ this.setTablePlain(table);
+
2024-10-25 21:24:15 +02:00
+ if (tableSize == MAXIMUM_CAPACITY) {
+ this.threshold = -1;
+ } else {
+ this.threshold = getTargetCapacity(tableSize, loadFactor);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
2024-10-25 21:24:15 +02:00
+ if (capacity >= MAXIMUM_CAPACITY) {
+ return MAXIMUM_CAPACITY;
+ }
+ return IntegerUtil.roundCeilLog2(capacity);
+ }
+
2024-10-25 21:24:15 +02:00
+ /** 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;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ /* 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@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();
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ return hash;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder(64);
+ builder.append("SingleWriterMultiReaderHashMap:{");
+
2024-10-25 21:24:15 +02:00
+ this.forEach((final long key, final V value) -> {
+ builder.append("{key: \"").append(key).append("\", value: \"").append(value).append("\"}");
+ });
+
2024-10-25 21:24:15 +02:00
+ return builder.append('}').toString();
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SWMRLong2ObjectHashTable<V> clone() {
+ return new SWMRLong2ObjectHashTable<>(this.getTableAcquire().length, this.loadFactor, this);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ public void forEach(final Consumer<? super TableEntry<V>> action) {
+ Validate.notNull(action, "Null action");
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ public void forEach(final BiLongObjectConsumer<? super V> action) {
+ Validate.notNull(action, "Null action");
+
2024-10-25 21:24:15 +02:00
+ 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();
+
2024-10-25 21:24:15 +02:00
+ action.accept(curr.key, value);
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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);
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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");
+
2024-10-25 21:24:15 +02:00
+ 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();
+
2024-10-25 21:24:15 +02:00
+ action.accept(value);
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ public V get(final long key) {
+ final TableEntry<V> entry = this.getEntryForOpaque(key);
+ return entry == null ? null : entry.getValueAcquire();
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@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;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ public V getOrDefault(final long key, final V defaultValue) {
+ final TableEntry<V> entry = this.getEntryForOpaque(key);
+
+ return entry == null ? defaultValue : entry.getValueAcquire();
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ public int size() {
+ return this.getSizeAcquire();
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isEmpty() {
2024-10-25 21:24:15 +02:00
+ return this.getSizeAcquire() == 0;
+ }
+
2024-10-25 21:24:15 +02:00
+ /* Non-MT-Safe */
+
2024-10-25 21:24:15 +02:00
+ protected int threshold;
+
2024-10-25 21:24:15 +02:00
+ protected final void checkResize(final int minCapacity) {
+ if (minCapacity <= this.threshold || this.threshold < 0) {
+ return;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
2024-10-25 21:24:15 +02:00
+ newCapacity = table.length << 1;
+ }
+
2024-10-25 21:24:15 +02:00
+ //noinspection unchecked
+ final TableEntry<V>[] newTable = new TableEntry[newCapacity];
+ final int indexMask = newCapacity - 1;
+
2024-10-25 21:24:15 +02:00
+ 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;
+
2024-10-25 21:24:15 +02:00
+ /* 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);
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ if (newCapacity == MAXIMUM_CAPACITY) {
+ this.threshold = -1; /* No more resizing */
+ } else {
2024-10-25 21:24:15 +02:00
+ this.threshold = getTargetCapacity(newCapacity, this.loadFactor);
+ }
2024-10-25 21:24:15 +02:00
+ this.setTableRelease(newTable); /* use release to publish entries in table */
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final int addToSize(final int num) {
+ final int newSize = this.getSizePlain() + num;
+
2024-10-25 21:24:15 +02:00
+ this.setSizeOpaque(newSize);
+ this.checkResize(newSize);
+
2024-10-25 21:24:15 +02:00
+ return newSize;
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final int removeFromSize(final int num) {
+ final int newSize = this.getSizePlain() - num;
+
2024-10-25 21:24:15 +02:00
+ this.setSizeOpaque(newSize);
+
2024-10-25 21:24:15 +02:00
+ return newSize;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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) {
2024-10-25 21:24:15 +02:00
+ final TableEntry<V> insert = new TableEntry<>(key, value);
+ setAtIndexRelease(table, index, insert);
+ this.addToSize(1);
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ for (TableEntry<V> curr = head;;) {
+ if (key == curr.key) {
+ if (onlyIfAbsent) {
+ return curr.getValuePlain();
+ }
+
2024-10-25 21:24:15 +02:00
+ final V currVal = curr.getValuePlain();
+ curr.setValueRelease(value);
+ return currVal;
+ }
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<V> next = curr.getNextPlain();
+ if (next != null) {
+ curr = next;
+ continue;
+ }
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<V> insert = new TableEntry<>(key, value);
+
2024-10-25 21:24:15 +02:00
+ curr.setNextRelease(insert);
+ this.addToSize(1);
+ return null;
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ public V put(final long key, final V value) {
+ Validate.notNull(value, "Null value");
+
2024-10-25 21:24:15 +02:00
+ return this.put(key, value, false);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ public V putIfAbsent(final long key, final V value) {
+ Validate.notNull(value, "Null value");
+
2024-10-25 21:24:15 +02:00
+ return this.put(key, value, true);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final V remove(final long key, final int hash) {
+ final TableEntry<V>[] table = this.getTablePlain();
+ final int index = (table.length - 1) & hash;
+
2024-10-25 21:24:15 +02:00
+ final TableEntry<V> head = table[index];
+ if (head == null) {
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (head.key == key) {
+ setAtIndexRelease(table, index, head.getNextPlain());
+ this.removeFromSize(1);
+
2024-10-25 21:24:15 +02:00
+ return head.getValuePlain();
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+
2024-10-25 21:24:15 +02:00
+ return curr.getValuePlain();
+ }
+ }
2024-10-25 21:24:15 +02:00
+
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (head.key == key) {
+ final V val = head.value;
+ if (val == expect || val.equals(expect)) {
+ setAtIndexRelease(table, index, head.getNextPlain());
+ this.removeFromSize(1);
+
2024-10-25 21:24:15 +02:00
+ return head.getValuePlain();
+ } else {
+ return null;
+ }
2024-10-25 21:24:15 +02:00
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+
2024-10-25 21:24:15 +02:00
+ return curr.getValuePlain();
+ } else {
2024-10-25 21:24:15 +02:00
+ return null;
+ }
+ }
+ }
2024-10-25 21:24:15 +02:00
+
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ public V remove(final long key) {
+ return this.remove(key, SWMRLong2ObjectHashTable.getHash(key));
+ }
+
2024-10-25 21:24:15 +02:00
+ public boolean remove(final long key, final V expect) {
+ return this.remove(key, SWMRLong2ObjectHashTable.getHash(key), expect) != null;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@inheritDoc}
+ */
+ public void putAll(final SWMRLong2ObjectHashTable<? extends V> map) {
+ Validate.notNull(map, "Null map");
+
2024-10-25 21:24:15 +02:00
+ final int size = map.size();
+ this.checkResize(Math.max(this.getSizePlain() + size/2, size)); /* preemptively resize */
+ map.forEach(this::put);
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * {@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);
+ }
+
2024-10-25 21:24:15 +02:00
+ public static final class TableEntry<V> {
+
2024-10-25 21:24:15 +02:00
+ protected static final VarHandle TABLE_ENTRY_ARRAY_HANDLE = ConcurrentUtil.getArrayHandle(TableEntry[].class);
+
2024-10-25 21:24:15 +02:00
+ protected final long key;
+ protected V value;
+
2024-10-25 21:24:15 +02:00
+ protected TableEntry<V> next;
+
2024-10-25 21:24:15 +02:00
+ 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);
+
2024-10-25 21:24:15 +02:00
+ /* value */
+
2024-10-25 21:24:15 +02:00
+ protected final V getValuePlain() {
+ //noinspection unchecked
+ return (V)VALUE_HANDLE.get(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final V getValueAcquire() {
+ //noinspection unchecked
+ return (V)VALUE_HANDLE.getAcquire(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setValueRelease(final V to) {
+ VALUE_HANDLE.setRelease(this, to);
+ }
+
2024-10-25 21:24:15 +02:00
+ /* next */
+
2024-10-25 21:24:15 +02:00
+ protected final TableEntry<V> getNextPlain() {
+ //noinspection unchecked
+ return (TableEntry<V>)NEXT_HANDLE.get(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final TableEntry<V> getNextOpaque() {
+ //noinspection unchecked
+ return (TableEntry<V>)NEXT_HANDLE.getOpaque(this);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setNextPlain(final TableEntry<V> next) {
+ NEXT_HANDLE.set(this, next);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final void setNextRelease(final TableEntry<V> next) {
+ NEXT_HANDLE.setRelease(this, next);
+ }
+
2024-10-25 21:24:15 +02:00
+ protected TableEntry(final long key, final V value) {
+ this.key = key;
+ this.value = value;
+ }
+
2024-10-25 21:24:15 +02:00
+ public long getKey() {
+ return this.key;
+ }
+
2024-10-25 21:24:15 +02:00
+ public V getValue() {
+ return this.getValueAcquire();
+ }
+ }
2024-10-25 21:24:15 +02:00
+}
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;
+
2024-10-25 21:24:15 +02:00
+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;
+
2024-10-25 21:24:15 +02:00
+/**
+ * @deprecated To be replaced
+ */
+@Deprecated
+public class SchedulerThreadPool {
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ return Long.compare(t1.id, t2.id);
+ };
+
2024-10-25 21:24:15 +02:00
+ 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;
+
2024-10-25 21:24:15 +02:00
+ private final Object scheduleLock = new Object();
+
2024-10-25 21:24:15 +02:00
+ private volatile boolean halted;
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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;
+
2024-10-25 21:24:15 +02:00
+ 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]);
+ }
+
2024-10-25 21:24:15 +02:00
+ this.threads = t;
+ this.runners = runners;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * Starts the threads in this pool.
+ */
+ public void start() {
+ for (final Thread thread : this.threads) {
+ thread.start();
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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;
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * Returns an array of the underlying scheduling threads.
+ */
+ public Thread[] getThreads() {
+ return this.threads.clone();
+ }
+
2024-10-25 21:24:15 +02:00
+ private void insertFresh(final SchedulableTick task) {
+ final TickThreadRunner[] runners = this.runners;
+
2024-10-25 21:24:15 +02:00
+ final int firstIdleThread = this.idleThreads.nextSetBit(0);
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ // try to replace the last awaiting task
+ final SchedulableTick last = this.awaiting.last();
+
2024-10-25 21:24:15 +02:00
+ 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);
+
2024-10-25 21:24:15 +02:00
+ final TickThreadRunner runner = last.ownedBy;
+ runner.replaceTask(task);
+
2024-10-25 21:24:15 +02:00
+ return;
+ }
+
2024-10-25 21:24:15 +02:00
+ // add to queue, will be picked up later
+ this.queued.add(task);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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");
+ }
+
2024-10-25 21:24:15 +02:00
+ task.schedulerOwnedBy = this;
+
2024-10-25 21:24:15 +02:00
+ this.insertFresh(task);
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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;
+ }
2024-10-25 21:24:15 +02:00
+ if (this.queued.remove(task)) {
+ task.setScheduledStart(newStart);
+ this.queued.add(task);
+ return true;
+ }
2024-10-25 21:24:15 +02:00
+ if (task.awaitingLink != null) {
+ this.awaiting.remove(task.awaitingLink);
+ task.awaitingLink = null;
+
2024-10-25 21:24:15 +02:00
+ // re-queue task
+ task.setScheduledStart(newStart);
+ this.queued.add(task);
+
2024-10-25 21:24:15 +02:00
+ // now we need to replace the task the runner was waiting for
+ final TickThreadRunner runner = task.ownedBy;
+ final SchedulableTick replace = this.queued.poll();
+
2024-10-25 21:24:15 +02:00
+ // replace cannot be null, since we have added a task to queued
+ if (replace != task) {
+ runner.replaceTask(replace);
+ }
+
2024-10-25 21:24:15 +02:00
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ return false;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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();
+
2024-10-25 21:24:15 +02:00
+ if (replace == null) {
+ // nothing to replace with, set to idle
+ this.idleThreads.set(runner.id);
+ runner.forceIdle();
+ } else {
+ runner.replaceTask(replace);
+ }
+
2024-10-25 21:24:15 +02:00
+ return Boolean.TRUE;
+ }
+
2024-10-25 21:24:15 +02:00
+ // could not find it in queue
+ return task.tryMarkCancelled() ? Boolean.FALSE : null;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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();
+
2024-10-25 21:24:15 +02:00
+ 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;
+
2024-10-25 21:24:15 +02:00
+ private final AtomicInteger scheduled = new AtomicInteger();
+ private SchedulerThreadPool schedulerOwnedBy;
+ private long scheduledStart = DEADLINE_NOT_SET;
+ private TickThreadRunner ownedBy;
+
2024-10-25 21:24:15 +02:00
+ private LinkedSortedSet.Link<SchedulableTick> awaitingLink;
+
2024-10-25 21:24:15 +02:00
+ private boolean tryMarkScheduled() {
+ return this.scheduled.compareAndSet(SCHEDULE_STATE_NOT_SCHEDULED, SCHEDULE_STATE_SCHEDULED);
+ }
+
2024-10-25 21:24:15 +02:00
+ private boolean tryMarkCancelled() {
+ return this.scheduled.compareAndSet(SCHEDULE_STATE_SCHEDULED, SCHEDULE_STATE_CANCELLED);
+ }
+
2024-10-25 21:24:15 +02:00
+ private boolean isScheduled() {
+ return this.scheduled.get() == SCHEDULE_STATE_SCHEDULED;
+ }
+
2024-10-25 21:24:15 +02:00
+ protected final long getScheduledStart() {
+ return this.scheduledStart;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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();
+
2024-10-25 21:24:15 +02:00
+ /**
+ * Returns whether this task has any intermediate tasks that can be executed.
+ */
+ public abstract boolean hasTasks();
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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);
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public String toString() {
+ return "SchedulableTick:{" +
+ "class=" + this.getClass().getName() + "," +
+ "scheduled_state=" + this.scheduled.get() + ","
+ + "}";
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ private static final class TickThreadRunner implements Runnable {
+
2024-10-25 21:24:15 +02:00
+ /**
+ * There are no tasks in this thread's runqueue, so it is parked.
+ * <p>
+ * stateTarget = null
+ * </p>
+ */
+ private static final int STATE_IDLE = 0;
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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;
+
2024-10-25 21:24:15 +02:00
+ /**
+ * 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;
+
2024-10-25 21:24:15 +02:00
+ public final int id;
+ public final SchedulerThreadPool scheduler;
+
2024-10-25 21:24:15 +02:00
+ 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);
+
2024-10-25 21:24:15 +02:00
+ private void setStatePlain(final TickThreadRunnerState state) {
+ STATE_HANDLE.set(this, state);
+ }
+
2024-10-25 21:24:15 +02:00
+ private void setStateOpaque(final TickThreadRunnerState state) {
+ STATE_HANDLE.setOpaque(this, state);
+ }
+
2024-10-25 21:24:15 +02:00
+ private void setStateVolatile(final TickThreadRunnerState state) {
+ STATE_HANDLE.setVolatile(this, state);
+ }
+
2024-10-25 21:24:15 +02:00
+ private static record TickThreadRunnerState(SchedulableTick stateTarget, int state) {}
+
2024-10-25 21:24:15 +02:00
+ public TickThreadRunner(final int id, final SchedulerThreadPool scheduler) {
+ this.id = id;
+ this.scheduler = scheduler;
+ }
+
2024-10-25 21:24:15 +02:00
+ private Thread getRunnerThread() {
+ return this.thread;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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());
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+
2024-10-25 21:24:15 +02:00
+ state.stateTarget.ownedBy = null;
+
2024-10-25 21:24:15 +02:00
+ this.setStateVolatile(new TickThreadRunnerState(task, STATE_AWAITING_TICK));
+ LockSupport.unpark(this.getRunnerThread());
+ }
+
2024-10-25 21:24:15 +02:00
+ private void forceIdle() {
+ final TickThreadRunnerState state = this.state;
+ if (state.state != STATE_AWAITING_TICK) {
+ throw new IllegalStateException("Cannot replace task in state " + state);
+ }
2024-10-25 21:24:15 +02:00
+ state.stateTarget.ownedBy = null;
+ this.setStateOpaque(new TickThreadRunnerState(null, STATE_IDLE));
+ // no need to unpark
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ private void returnTask(final SchedulableTick task, final boolean reschedule) {
+ synchronized (this.scheduler.scheduleLock) {
+ task.ownedBy = null;
+
2024-10-25 21:24:15 +02:00
+ 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));
+ }
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public void run() {
+ this.thread = Thread.currentThread();
+
2024-10-25 21:24:15 +02:00
+ main_state_loop:
+ for (;;) {
+ final TickThreadRunnerState startState = this.state;
+ final int startStateType = startState.state;
+ final SchedulableTick startStateTask = startState.stateTarget;
+
2024-10-25 21:24:15 +02:00
+ if (this.scheduler.halted) {
+ return;
+ }
+
2024-10-25 21:24:15 +02:00
+ switch (startStateType) {
+ case STATE_IDLE: {
+ while (this.state.state == STATE_IDLE) {
+ LockSupport.park();
+ if (this.scheduler.halted) {
+ return;
+ }
+ }
+ continue main_state_loop;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ if (!this.takeTask(startState, startStateTask)) {
+ continue main_state_loop;
+ }
+
2024-10-25 21:24:15 +02:00
+ // TODO exception handling
+ final boolean reschedule = startStateTask.runTick();
+
2024-10-25 21:24:15 +02:00
+ this.returnTask(startStateTask, reschedule);
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+ }
+ }
+ }
+ }
2024-10-25 21:24:15 +02:00
+}
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;
+
2024-10-25 21:24:15 +02:00
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
2024-10-25 21:24:15 +02:00
+public final class LinkedSortedSet<E> implements Iterable<E> {
+
2024-10-25 21:24:15 +02:00
+ public final Comparator<? super E> comparator;
+
+ private Link<E> head;
+ private Link<E> tail;
+
2024-10-25 21:24:15 +02:00
+ public LinkedSortedSet() {
+ this((Comparator)Comparator.naturalOrder());
+ }
+
2024-10-25 21:24:15 +02:00
+ public LinkedSortedSet(final Comparator<? super E> comparator) {
+ this.comparator = comparator;
+ }
+
2024-10-25 21:24:15 +02:00
+ public void clear() {
+ this.head = this.tail = null;
+ }
+
2024-10-25 21:24:15 +02:00
+ public boolean isEmpty() {
+ return this.head == null;
+ }
+
2024-10-25 21:24:15 +02:00
+ public E first() {
+ final Link<E> head = this.head;
+ return head == null ? null : head.element;
+ }
+
2024-10-25 21:24:15 +02:00
+ public E last() {
+ final Link<E> tail = this.tail;
+ return tail == null ? null : tail.element;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
2024-10-25 21:24:15 +02:00
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+
2024-10-25 21:24:15 +02:00
+ private void removeNode(final Link<E> node) {
+ final Link<E> prev = node.prev;
+ final Link<E> next = node.next;
+
2024-10-25 21:24:15 +02:00
+ // help GC
+ node.element = null;
+ node.prev = null;
+ node.next = null;
+
2024-10-25 21:24:15 +02:00
+ if (prev == null) {
+ this.head = next;
+ } else {
+ prev.next = next;
+ }
+
2024-10-25 21:24:15 +02:00
+ if (next == null) {
+ this.tail = prev;
+ } else {
+ next.prev = prev;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ public boolean remove(final Link<E> link) {
+ if (link.element == null) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ this.removeNode(link);
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
2024-10-25 21:24:15 +02:00
+ }
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
2024-10-25 21:24:15 +02:00
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public Iterator<E> iterator() {
+ return new Iterator<>() {
+ private Link<E> next = LinkedSortedSet.this.head;
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean hasNext() {
+ return this.next != null;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public E next() {
+ final Link<E> next = this.next;
+ if (next == null) {
+ throw new NoSuchElementException();
+ }
+ this.next = next.next;
+ return next.element;
+ }
2024-10-25 21:24:15 +02:00
+ };
+ }
+
2024-10-25 21:24:15 +02:00
+ public E pollFirst() {
+ final Link<E> head = this.head;
+ if (head == null) {
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ final E ret = head.element;
+ final Link<E> next = head.next;
+
2024-10-25 21:24:15 +02:00
+ // unlink head
+ this.head = next;
+ if (next == null) {
+ this.tail = null;
+ } else {
+ next.prev = null;
+ }
2024-10-25 21:24:15 +02:00
+
+ // help GC
+ head.element = null;
+ head.next = null;
+
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ public E pollLast() {
+ final Link<E> tail = this.tail;
+ if (tail == null) {
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ final E ret = tail.element;
+ final Link<E> prev = tail.prev;
+
2024-10-25 21:24:15 +02:00
+ // unlink tail
+ this.tail = prev;
+ if (prev == null) {
+ this.head = null;
+ } else {
+ prev.next = null;
+ }
2024-10-25 21:24:15 +02:00
+
+ // help GC
+ tail.element = null;
+ tail.prev = null;
+
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ public Link<E> addLast(final E element) {
+ final Comparator<? super E> comparator = this.comparator;
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
2024-10-25 21:24:15 +02:00
+
+ return null;
+ } else {
+ return this.head = this.tail = new Link<>(element);
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ public Link<E> addFirst(final E element) {
+ final Comparator<? super E> comparator = this.comparator;
+
2024-10-25 21:24:15 +02:00
+ Link<E> curr = this.head;
+ if (curr != null) {
+ int compare;
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+
2024-10-25 21:24:15 +02:00
+ if (compare != 0) {
+ // insert before curr
+ final Link<E> prev = curr.prev;
+ final Link<E> insert = new Link<>(element, prev, curr);
+ curr.prev = insert;
+
2024-10-25 21:24:15 +02:00
+ if (prev == null) {
+ this.head = insert;
+ } else {
+ prev.next = insert;
+ }
+ return insert;
+ }
+
2024-10-25 21:24:15 +02:00
+ return null;
+ } else {
+ return this.head = this.tail = new Link<>(element);
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ public static final class Link<E> {
+ private E element;
+ private Link<E> prev;
+ private Link<E> next;
+
2024-10-25 21:24:15 +02:00
+ private Link(final E element) {
+ this.element = element;
+ }
+
2024-10-25 21:24:15 +02:00
+ private Link(final E element, final Link<E> prev, final Link<E> next) {
+ this.element = element;
+ this.prev = prev;
+ this.next = next;
+ }
+ }
2024-10-25 21:24:15 +02:00
+}
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;
+
2024-10-25 21:24:15 +02:00
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
2024-10-25 21:24:15 +02:00
+public final class LinkedUnsortedList<E> implements Iterable<E> {
+
2024-10-25 21:24:15 +02:00
+ private Link<E> head;
+ private Link<E> tail;
+
2024-10-25 21:24:15 +02:00
+ public LinkedUnsortedList() {}
+
2024-10-25 21:24:15 +02:00
+ public void clear() {
+ this.head = this.tail = null;
+ }
+
2024-10-25 21:24:15 +02:00
+ public boolean isEmpty() {
+ return this.head == null;
+ }
+
2024-10-25 21:24:15 +02:00
+ public E first() {
+ final Link<E> head = this.head;
+ return head == null ? null : head.element;
+ }
+
2024-10-25 21:24:15 +02:00
+ public E last() {
+ final Link<E> tail = this.tail;
+ return tail == null ? null : tail.element;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
2024-10-25 21:24:15 +02:00
+ }
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
2024-10-25 21:24:15 +02:00
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ private void removeNode(final Link<E> node) {
+ final Link<E> prev = node.prev;
+ final Link<E> next = node.next;
+
2024-10-25 21:24:15 +02:00
+ // help GC
+ node.element = null;
+ node.prev = null;
+ node.next = null;
+
2024-10-25 21:24:15 +02:00
+ if (prev == null) {
+ this.head = next;
+ } else {
+ prev.next = next;
+ }
+
+ if (next == null) {
+ this.tail = prev;
+ } else {
+ next.prev = prev;
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ public boolean remove(final Link<E> link) {
+ if (link.element == null) {
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ this.removeNode(link);
+ return true;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
2024-10-25 21:24:15 +02:00
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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;
+ }
+ }
2024-10-25 21:24:15 +02:00
+ return false;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public Iterator<E> iterator() {
+ return new Iterator<>() {
+ private Link<E> next = LinkedUnsortedList.this.head;
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public boolean hasNext() {
+ return this.next != null;
+ }
+
2024-10-25 21:24:15 +02:00
+ @Override
+ public E next() {
+ final Link<E> next = this.next;
+ if (next == null) {
+ throw new NoSuchElementException();
+ }
+ this.next = next.next;
+ return next.element;
+ }
+ };
+ }
+
2024-10-25 21:24:15 +02:00
+ public E pollFirst() {
+ final Link<E> head = this.head;
+ if (head == null) {
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ final E ret = head.element;
+ final Link<E> next = head.next;
+
2024-10-25 21:24:15 +02:00
+ // unlink head
+ this.head = next;
+ if (next == null) {
+ this.tail = null;
+ } else {
+ next.prev = null;
+ }
+
2024-10-25 21:24:15 +02:00
+ // help GC
+ head.element = null;
+ head.next = null;
+
2024-10-25 21:24:15 +02:00
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ public E pollLast() {
+ final Link<E> tail = this.tail;
+ if (tail == null) {
+ return null;
+ }
+
2024-10-25 21:24:15 +02:00
+ final E ret = tail.element;
+ final Link<E> prev = tail.prev;
+
2024-10-25 21:24:15 +02:00
+ // unlink tail
+ this.tail = prev;
+ if (prev == null) {
+ this.head = null;
+ } else {
+ prev.next = null;
+ }
+
2024-10-25 21:24:15 +02:00
+ // help GC
+ tail.element = null;
+ tail.prev = null;
+
+ return ret;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ 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);
+ }
+ }
+
2024-10-25 21:24:15 +02:00
+ public static final class Link<E> {
+ private E element;
+ private Link<E> prev;
+ private Link<E> next;
+
2024-10-25 21:24:15 +02:00
+ private Link(final E element) {
+ this.element = element;
+ }
+
2024-10-25 21:24:15 +02:00
+ 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
2024-10-25 21:24:15 +02:00
index 0000000000000000000000000000000000000000..9d7b9b8158cd01d12adbd7896ff77bee9828e101
--- /dev/null
+++ b/src/main/java/ca/spottedleaf/concurrentutil/util/IntegerUtil.java
2024-10-25 21:24:15 +02:00
@@ -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
+ }
+
2024-10-25 21:24:15 +02:00
+ // 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
2024-10-25 21:24:15 +02:00
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();
+ }
+}