package jalse.actions;
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* A thread-safe queue for {@link AbstractManualActionContext} using the estimated perform time (
* {@link AbstractManualActionContext#getEstimated()}). This is a convenience class for creating an
* {@link ActionEngine}.
*
* @author Elliot Ford
*
* @param <T>
* Actor type.
*
* @see ManualActionEngine
* @see ForkJoinActionEngine
*/
public class ManualWorkQueue<T extends AbstractManualActionContext<?>> {
private final Queue<T> waitingWork;
private final Lock read;
private final Lock write;
private final Condition workChanged;
/**
* Creates a new instance of ManualWorkQueue.
*/
public ManualWorkQueue() {
waitingWork = new PriorityQueue<>();
final ReadWriteLock rwLock = new ReentrantReadWriteLock();
read = rwLock.readLock();
write = rwLock.writeLock();
workChanged = write.newCondition();
}
/**
* Adds work to the queue.
*
* @param context
* Work to add.
* @return Whether the work was not previously within the queue.
*/
public boolean addWaitingWork(final T context) {
write.lock();
try {
boolean result = !waitingWork.contains(context);
if (result) {
waitingWork.add(context);
workChanged.signalAll(); // Wake up!
}
return result;
} finally {
write.unlock();
}
}
/**
* Awaits until the next work is ready (or the work queue is empty).
*
* @throws InterruptedException
* Whether the wait is interrupted.
*/
public void awaitNextReadyWork() throws InterruptedException {
write.lockInterruptibly();
try {
long next = getEarliestReadyEstimate();
while (!workAvailable() && !waitingWork.isEmpty()) {
if (next > 0L) {
workChanged.awaitNanos(next - System.nanoTime()); // Or signal
next = getEarliestReadyEstimate(); // Might be new work added?
}
}
} finally {
write.unlock();
}
}
private long getEarliestReadyEstimate() {
final T context = waitingWork.peek();
return context != null ? context.getEstimated() : 0L;
}
/**
* Gets all waiting work in the queue.
*
* @return All waiting work.
*/
public List<? extends T> getWaitingWork() {
read.lock();
try {
return new ArrayList<>(waitingWork);
} finally {
read.unlock();
}
}
/**
* Whether the work is contained in the queue.
*
* @param context
* Work to check.
* @return Whether the work was already waiting.
*/
public boolean isWaitingWork(final T context) {
read.lock();
try {
return waitingWork.contains(context);
} finally {
read.unlock();
}
}
/**
* Whether there is work ready.
*
* @return Whether work is ready.
*/
public boolean isWorkReady() {
read.lock();
try {
return workAvailable();
} finally {
read.unlock();
}
}
/**
* Whether the queue has waiting work.
*
* @return Whether the queue is not empty.
*/
public boolean isWorkWaiting() {
return waitingWorkSize() > 0;
}
/**
* Polls for ready work (removes if work is available).
*
* @return Work if available (or else null).
*/
public T pollReadyWork() {
write.lock();
try {
return workAvailable() ? waitingWork.poll() : null;
} finally {
write.unlock();
}
}
/**
* Clears waiting work.
*/
public void removeAllWaitingWork() {
write.lock();
try {
waitingWork.clear();
workChanged.signalAll(); // Wake up!
} finally {
write.unlock();
}
}
/**
* Removes work from the queue.
*
* @param context
* Work to remove.
* @return Whether the work was already in the queue.
*/
public boolean removeWaitingWork(final T context) {
write.lock();
try {
boolean result = waitingWork.remove(context);
if (result) {
workChanged.signalAll();
}
return result;
} finally {
write.unlock();
}
}
/**
* Gets the waiting work count.
*
* @return Work count.
*/
public int waitingWorkSize() {
read.lock();
try {
return waitingWork.size();
} finally {
read.unlock();
}
}
private boolean workAvailable() {
final T work = waitingWork.peek();
return work != null && System.nanoTime() >= work.getEstimated();
}
}