package com.intellectualcrafters.plot.util.block;
import com.intellectualcrafters.plot.PS;
import com.intellectualcrafters.plot.object.RunnableVal2;
import com.intellectualcrafters.plot.util.TaskManager;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicBoolean;
public class GlobalBlockQueue {
public static GlobalBlockQueue IMP;
private final int PARALLEL_THREADS;
private QueueProvider provider;
private final ConcurrentLinkedDeque<LocalBlockQueue> activeQueues;
private final ConcurrentLinkedDeque<LocalBlockQueue> inactiveQueues;
private final ConcurrentLinkedDeque<Runnable> runnables;
/**
* Used to calculate elapsed time in milliseconds and ensure block placement doesn't lag the server
*/
private long last;
private long secondLast;
private long lastSuccess;
private final AtomicBoolean running;
public enum QueueStage {
INACTIVE, ACTIVE, NONE;
}
public GlobalBlockQueue(QueueProvider provider, int threads) {
this.provider = provider;
activeQueues = new ConcurrentLinkedDeque<>();
inactiveQueues = new ConcurrentLinkedDeque<>();
runnables = new ConcurrentLinkedDeque<>();
running = new AtomicBoolean();
this.PARALLEL_THREADS = threads;
}
private final RunnableVal2<Long, LocalBlockQueue> SET_TASK = new RunnableVal2<Long, LocalBlockQueue>() {
@Override
public void run(Long free, LocalBlockQueue queue) {
do {
boolean more = queue.next();
if (!more) {
lastSuccess = last;
if (inactiveQueues.size() == 0 && activeQueues.size() == 0) {
tasks();
}
return;
}
} while (((GlobalBlockQueue.this.secondLast = System.currentTimeMillis()) - GlobalBlockQueue.this.last) < free);
}
};
public QueueProvider getProvider() {
return provider;
}
public void setProvider(QueueProvider provider) {
this.provider = provider;
}
public LocalBlockQueue getNewQueue(String world, boolean autoQueue) {
LocalBlockQueue queue = provider.getNewQueue(world);
if (autoQueue) {
inactiveQueues.add(queue);
}
return queue;
}
public boolean stop() {
if (!running.get()) {
return false;
}
running.set(false);
return true;
}
public boolean runTask() {
if (running.get()) {
return false;
}
running.set(true);
TaskManager.runTaskRepeat(new Runnable() {
@Override
public void run() {
if (inactiveQueues.isEmpty() && activeQueues.isEmpty()) {
lastSuccess = System.currentTimeMillis();
tasks();
return;
}
SET_TASK.value1 = 50 + Math.min((50 + GlobalBlockQueue.this.last) - (GlobalBlockQueue.this.last = System.currentTimeMillis()), GlobalBlockQueue.this.secondLast - System.currentTimeMillis());
SET_TASK.value2 = getNextQueue();
if (SET_TASK.value2 == null) {
return;
}
if (!PS.get().isMainThread(Thread.currentThread())) {
throw new IllegalStateException("This shouldn't be possible for placement to occur off the main thread");
}
// Disable the async catcher as it can't discern async vs parallel
SET_TASK.value2.startSet(true);
try {
if (PARALLEL_THREADS <= 1) {
SET_TASK.run();
} else {
ArrayList<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < PARALLEL_THREADS; i++) {
threads.add(new Thread(SET_TASK));
}
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
// Enable it again (note that we are still on the main thread)
SET_TASK.value2.endSet(true);
}
}
}, 1);
return true;
}
public QueueStage getStage(LocalBlockQueue queue) {
if (activeQueues.contains(queue)) {
return QueueStage.ACTIVE;
} else if (inactiveQueues.contains(queue)) {
return QueueStage.INACTIVE;
}
return QueueStage.NONE;
}
public boolean isStage(LocalBlockQueue queue, QueueStage stage) {
switch (stage) {
case ACTIVE:
return activeQueues.contains(queue);
case INACTIVE:
return inactiveQueues.contains(queue);
case NONE:
return !activeQueues.contains(queue) && !inactiveQueues.contains(queue);
}
return false;
}
public void enqueue(LocalBlockQueue queue) {
inactiveQueues.remove(queue);
if (queue.size() > 0 && !activeQueues.contains(queue)) {
queue.optimize();
activeQueues.add(queue);
}
}
public void dequeue(LocalBlockQueue queue) {
inactiveQueues.remove(queue);
activeQueues.remove(queue);
}
public List<LocalBlockQueue> getAllQueues() {
ArrayList<LocalBlockQueue> list = new ArrayList<LocalBlockQueue>(activeQueues.size() + inactiveQueues.size());
list.addAll(inactiveQueues);
list.addAll(activeQueues);
return list;
}
public List<LocalBlockQueue> getActiveQueues() {
return new ArrayList<>(activeQueues);
}
public List<LocalBlockQueue> getInactiveQueues() {
return new ArrayList<>(inactiveQueues);
}
public void flush(LocalBlockQueue queue) {
SET_TASK.value1 = Long.MAX_VALUE;
SET_TASK.value2 = queue;
if (SET_TASK.value2 == null) {
return;
}
if (PS.get().isMainThread(Thread.currentThread())) {
throw new IllegalStateException("Must be flushed on the main thread!");
}
// Disable the async catcher as it can't discern async vs parallel
SET_TASK.value2.startSet(true);
try {
if (PARALLEL_THREADS <= 1) {
SET_TASK.run();
} else {
ArrayList<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < PARALLEL_THREADS; i++) {
threads.add(new Thread(SET_TASK));
}
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
// Enable it again (note that we are still on the main thread)
SET_TASK.value2.endSet(true);
dequeue(queue);
}
}
public LocalBlockQueue getNextQueue() {
long now = System.currentTimeMillis();
while (activeQueues.size() > 0) {
LocalBlockQueue queue = activeQueues.peek();
if (queue != null && queue.size() > 0) {
queue.setModified(now);
return queue;
} else {
activeQueues.poll();
}
}
int size = inactiveQueues.size();
if (size > 0) {
Iterator<LocalBlockQueue> iter = inactiveQueues.iterator();
try {
int total = 0;
LocalBlockQueue firstNonEmpty = null;
while (iter.hasNext()) {
LocalBlockQueue queue = iter.next();
long age = now - queue.getModified();
total += queue.size();
if (queue.size() == 0) {
if (age > 1000) {
iter.remove();
}
continue;
}
if (firstNonEmpty == null) {
firstNonEmpty = queue;
}
if (total > 64) {
firstNonEmpty.setModified(now);
return firstNonEmpty;
}
if (age > 60000) {
queue.setModified(now);
return queue;
}
}
} catch (ConcurrentModificationException e) {
e.printStackTrace();
}
}
return null;
}
public boolean isDone() {
return activeQueues.size() == 0 && inactiveQueues.size() == 0;
}
public boolean addTask(final Runnable whenDone) {
if (this.isDone()) {
// Run
this.tasks();
if (whenDone != null) {
whenDone.run();
}
return true;
}
if (whenDone != null) {
this.runnables.add(whenDone);
}
return false;
}
public synchronized boolean tasks() {
if (this.runnables.isEmpty()) {
return false;
}
final ArrayList<Runnable> tmp = new ArrayList<>(this.runnables);
this.runnables.clear();
for (final Runnable runnable : tmp) {
runnable.run();
}
return true;
}
}