mirror of
https://github.com/boy0001/FastAsyncWorldedit.git
synced 2024-09-27 06:32:40 +02:00
451 lines
16 KiB
Java
451 lines
16 KiB
Java
package com.boydti.fawe.util;
|
|
|
|
import com.boydti.fawe.Fawe;
|
|
import com.boydti.fawe.config.Settings;
|
|
import com.boydti.fawe.object.FaweQueue;
|
|
import com.boydti.fawe.wrappers.WorldWrapper;
|
|
import com.sk89q.worldedit.world.World;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.ConcurrentModificationException;
|
|
import java.util.Iterator;
|
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
|
import java.util.concurrent.ExecutorCompletionService;
|
|
import java.util.concurrent.ForkJoinPool;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
public class SetQueue {
|
|
|
|
/**
|
|
* The implementation specific queue
|
|
*/
|
|
public static final SetQueue IMP = new SetQueue();
|
|
private double targetTPS = 18;
|
|
|
|
public enum QueueStage {
|
|
INACTIVE, ACTIVE, NONE;
|
|
}
|
|
|
|
private final ConcurrentLinkedDeque<FaweQueue> activeQueues;
|
|
private final ConcurrentLinkedDeque<FaweQueue> inactiveQueues;
|
|
private final ConcurrentLinkedDeque<Runnable> tasks;
|
|
|
|
/**
|
|
* Used to calculate elapsed time in milliseconds and ensure block placement doesn't lag the server
|
|
*/
|
|
private long last;
|
|
private long allocate = 50;
|
|
private long lastSuccess;
|
|
|
|
/**
|
|
* A queue of tasks that will run when the queue is empty
|
|
*/
|
|
private final ConcurrentLinkedDeque<Runnable> emptyTasks = new ConcurrentLinkedDeque<>();
|
|
|
|
private ForkJoinPool pool = new ForkJoinPool();
|
|
private ExecutorCompletionService completer = new ExecutorCompletionService(pool);
|
|
|
|
/**
|
|
* @return ForkJoinPool
|
|
* @see TaskManager#getPublicForkJoinPool()
|
|
*/
|
|
@Deprecated
|
|
public ExecutorCompletionService getCompleterService() {
|
|
return completer;
|
|
}
|
|
|
|
@Deprecated
|
|
public ForkJoinPool getForkJoinPool() {
|
|
return pool;
|
|
}
|
|
|
|
public void runMiscTasks() {
|
|
while (Fawe.get().getTimer().isAbove(targetTPS)) {
|
|
Runnable task = tasks.poll();
|
|
if (task != null) {
|
|
task.run();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public SetQueue() {
|
|
tasks = new ConcurrentLinkedDeque<>();
|
|
activeQueues = new ConcurrentLinkedDeque();
|
|
inactiveQueues = new ConcurrentLinkedDeque<>();
|
|
if (TaskManager.IMP == null) return;
|
|
TaskManager.IMP.repeat(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
long now = System.currentTimeMillis();
|
|
boolean empty = (inactiveQueues.isEmpty() && activeQueues.isEmpty());
|
|
boolean emptyTasks = tasks.isEmpty();
|
|
if (emptyTasks && empty) {
|
|
last = now;
|
|
runEmptyTasks();
|
|
return;
|
|
}
|
|
|
|
targetTPS = 18 - Math.max(Settings.IMP.QUEUE.EXTRA_TIME_MS * 0.05, 0);
|
|
|
|
long diff = (50 + SetQueue.this.last) - (SetQueue.this.last = now);
|
|
long absDiff = Math.abs(diff);
|
|
if (diff == 0) {
|
|
allocate = Math.min(50, allocate + 1);
|
|
} else if (diff < 0) {
|
|
allocate = Math.max(5, allocate + diff);
|
|
} else if (!Fawe.get().getTimer().isAbove(targetTPS)) {
|
|
allocate = Math.max(5, allocate - 1);
|
|
}
|
|
|
|
long currentAllocate = allocate - absDiff;
|
|
|
|
if (!emptyTasks) {
|
|
long taskAllocate = activeQueues.isEmpty() ? currentAllocate : 1 + (currentAllocate >> 1);
|
|
long used = 0;
|
|
boolean wait = false;
|
|
do {
|
|
Runnable task = tasks.poll();
|
|
if (task == null) {
|
|
if (wait) {
|
|
synchronized (tasks) {
|
|
tasks.wait(1);
|
|
}
|
|
task = tasks.poll();
|
|
wait = false;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (task != null) {
|
|
task.run();
|
|
wait = true;
|
|
}
|
|
} while ((used = System.currentTimeMillis() - now) < taskAllocate);
|
|
currentAllocate -= used;
|
|
}
|
|
|
|
if (empty) {
|
|
runEmptyTasks();
|
|
return;
|
|
}
|
|
|
|
if (!MemUtil.isMemoryFree()) {
|
|
final int mem = MemUtil.calculateMemory();
|
|
if (mem != Integer.MAX_VALUE) {
|
|
allocate = Math.max(5, allocate - 1);
|
|
if ((mem <= 1) && Settings.IMP.PREVENT_CRASHES) {
|
|
for (FaweQueue queue : getAllQueues()) {
|
|
queue.saveMemory();
|
|
}
|
|
return;
|
|
}
|
|
if (SetQueue.this.forceChunkSet()) {
|
|
System.gc();
|
|
} else {
|
|
SetQueue.this.runEmptyTasks();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
FaweQueue queue = getNextQueue();
|
|
if (queue == null) {
|
|
return;
|
|
}
|
|
|
|
long time = Settings.IMP.QUEUE.EXTRA_TIME_MS + currentAllocate - System.currentTimeMillis() + now;
|
|
// Disable the async catcher as it can't discern async vs parallel
|
|
boolean parallel = Settings.IMP.QUEUE.PARALLEL_THREADS > 1;
|
|
queue.startSet(parallel);
|
|
try {
|
|
if (!queue.next(Settings.IMP.QUEUE.PARALLEL_THREADS, time) && queue.getStage() == QueueStage.ACTIVE) {
|
|
queue.setStage(QueueStage.NONE);
|
|
queue.runTasks();
|
|
}
|
|
} catch (Throwable e) {
|
|
pool.awaitQuiescence(Settings.IMP.QUEUE.DISCARD_AFTER_MS, TimeUnit.MILLISECONDS);
|
|
completer = new ExecutorCompletionService(pool);
|
|
e.printStackTrace();
|
|
}
|
|
if (pool.getQueuedSubmissionCount() != 0 || pool.getRunningThreadCount() != 0 || pool.getQueuedTaskCount() != 0) {
|
|
// if (Fawe.get().isJava8())
|
|
{
|
|
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
|
|
}
|
|
// else {
|
|
// pool.shutdown();
|
|
// pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
|
|
// pool = new ForkJoinPool();
|
|
// completer = new ExecutorCompletionService(pool);
|
|
// }
|
|
}
|
|
queue.endSet(parallel);
|
|
} catch (Throwable e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}, 1);
|
|
}
|
|
|
|
public QueueStage getStage(FaweQueue queue) {
|
|
return queue.getStage();
|
|
}
|
|
|
|
public boolean isStage(FaweQueue 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 boolean enqueue(FaweQueue queue) {
|
|
queue.setStage(QueueStage.ACTIVE);
|
|
inactiveQueues.remove(queue);
|
|
if (queue.size() > 0) {
|
|
if (!activeQueues.contains(queue)) {
|
|
queue.optimize();
|
|
activeQueues.add(queue);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void dequeue(FaweQueue queue) {
|
|
queue.setStage(QueueStage.NONE);
|
|
inactiveQueues.remove(queue);
|
|
activeQueues.remove(queue);
|
|
queue.runTasks();
|
|
}
|
|
|
|
public Collection<FaweQueue> getAllQueues() {
|
|
ArrayList<FaweQueue> list = new ArrayList<FaweQueue>(activeQueues.size() + inactiveQueues.size());
|
|
list.addAll(inactiveQueues);
|
|
list.addAll(activeQueues);
|
|
return list;
|
|
}
|
|
|
|
public Collection<FaweQueue> getActiveQueues() {
|
|
return Collections.unmodifiableCollection(activeQueues);
|
|
}
|
|
|
|
public Collection<FaweQueue> getInactiveQueues() {
|
|
return Collections.unmodifiableCollection(inactiveQueues);
|
|
}
|
|
|
|
public FaweQueue getNewQueue(World world, boolean fast, boolean autoqueue) {
|
|
world = WorldWrapper.unwrap(world);
|
|
if (world instanceof FaweQueue) return (FaweQueue) world;
|
|
FaweQueue queue = Fawe.imp().getNewQueue(world, fast);
|
|
if (autoqueue) {
|
|
queue.setStage(QueueStage.INACTIVE);
|
|
inactiveQueues.add(queue);
|
|
}
|
|
return queue;
|
|
}
|
|
|
|
public FaweQueue getNewQueue(String world, boolean fast, boolean autoqueue) {
|
|
FaweQueue queue = Fawe.imp().getNewQueue(world, fast);
|
|
if (autoqueue) {
|
|
queue.setStage(QueueStage.INACTIVE);
|
|
inactiveQueues.add(queue);
|
|
}
|
|
return queue;
|
|
}
|
|
|
|
public void flush(FaweQueue queue) {
|
|
int parallelThreads;
|
|
if (Fawe.get().isMainThread()) {
|
|
parallelThreads = Settings.IMP.QUEUE.PARALLEL_THREADS;
|
|
Settings.IMP.QUEUE.PARALLEL_THREADS = 1;
|
|
} else {
|
|
parallelThreads = 0;
|
|
}
|
|
try {
|
|
queue.startSet(Settings.IMP.QUEUE.PARALLEL_THREADS > 1);
|
|
queue.next(Settings.IMP.QUEUE.PARALLEL_THREADS, Long.MAX_VALUE);
|
|
} catch (Throwable e) {
|
|
pool.awaitQuiescence(Settings.IMP.QUEUE.DISCARD_AFTER_MS, TimeUnit.MILLISECONDS);
|
|
completer = new ExecutorCompletionService(pool);
|
|
MainUtil.handleError(e);
|
|
} finally {
|
|
queue.endSet(Settings.IMP.QUEUE.PARALLEL_THREADS > 1);
|
|
queue.setStage(QueueStage.NONE);
|
|
queue.runTasks();
|
|
if (parallelThreads != 0) {
|
|
Settings.IMP.QUEUE.PARALLEL_THREADS = parallelThreads;
|
|
}
|
|
}
|
|
}
|
|
|
|
public FaweQueue getNextQueue() {
|
|
long now = System.currentTimeMillis();
|
|
while (!activeQueues.isEmpty()) {
|
|
FaweQueue queue = activeQueues.peek();
|
|
if (queue != null && queue.size() > 0) {
|
|
queue.setModified(now);
|
|
return queue;
|
|
} else {
|
|
queue.setStage(QueueStage.NONE);
|
|
queue.runTasks();
|
|
activeQueues.poll();
|
|
}
|
|
}
|
|
int size = inactiveQueues.size();
|
|
if (size > 0) {
|
|
Iterator<FaweQueue> iter = inactiveQueues.iterator();
|
|
try {
|
|
int total = 0;
|
|
FaweQueue firstNonEmpty = null;
|
|
while (iter.hasNext()) {
|
|
FaweQueue queue = iter.next();
|
|
long age = now - queue.getModified();
|
|
total += queue.size();
|
|
if (queue.size() == 0) {
|
|
if (age > Settings.IMP.QUEUE.DISCARD_AFTER_MS) {
|
|
queue.setStage(QueueStage.NONE);
|
|
queue.runTasks();
|
|
iter.remove();
|
|
}
|
|
continue;
|
|
}
|
|
if (firstNonEmpty == null) {
|
|
firstNonEmpty = queue;
|
|
}
|
|
if (total > Settings.IMP.QUEUE.TARGET_SIZE) {
|
|
firstNonEmpty.setModified(now);
|
|
return firstNonEmpty;
|
|
}
|
|
if (age > Settings.IMP.QUEUE.MAX_WAIT_MS) {
|
|
queue.setModified(now);
|
|
return queue;
|
|
}
|
|
}
|
|
} catch (ConcurrentModificationException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public boolean next() {
|
|
while (activeQueues.size() > 0) {
|
|
FaweQueue queue = activeQueues.poll();
|
|
if (queue != null) {
|
|
final boolean set = queue.next();
|
|
if (set) {
|
|
activeQueues.add(queue);
|
|
return set;
|
|
} else {
|
|
queue.setStage(QueueStage.NONE);
|
|
queue.runTasks();
|
|
}
|
|
}
|
|
}
|
|
if (inactiveQueues.size() > 0) {
|
|
ArrayList<FaweQueue> tmp = new ArrayList<>(inactiveQueues);
|
|
if (Settings.IMP.QUEUE.MAX_WAIT_MS != -1) {
|
|
long now = System.currentTimeMillis();
|
|
if (lastSuccess == 0) {
|
|
lastSuccess = now;
|
|
}
|
|
long diff = now - lastSuccess;
|
|
if (diff > Settings.IMP.QUEUE.MAX_WAIT_MS) {
|
|
for (FaweQueue queue : tmp) {
|
|
boolean result = queue.next();
|
|
if (result) {
|
|
return result;
|
|
}
|
|
}
|
|
if (diff > Settings.IMP.QUEUE.DISCARD_AFTER_MS) {
|
|
// These edits never finished
|
|
for (FaweQueue queue : tmp) {
|
|
queue.setStage(QueueStage.NONE);
|
|
queue.runTasks();
|
|
}
|
|
inactiveQueues.clear();
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
if (Settings.IMP.QUEUE.TARGET_SIZE != -1) {
|
|
int total = 0;
|
|
for (FaweQueue queue : tmp) {
|
|
total += queue.size();
|
|
}
|
|
if (total > Settings.IMP.QUEUE.TARGET_SIZE) {
|
|
for (FaweQueue queue : tmp) {
|
|
boolean result = queue.next();
|
|
if (result) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public boolean forceChunkSet() {
|
|
return next();
|
|
}
|
|
|
|
/**
|
|
* Is the this empty
|
|
*
|
|
* @return
|
|
*/
|
|
public boolean isEmpty() {
|
|
return activeQueues.size() == 0 && inactiveQueues.size() == 0;
|
|
}
|
|
|
|
public void addTask(Runnable whenFree) {
|
|
tasks.add(whenFree);
|
|
synchronized (tasks) {
|
|
tasks.notifyAll();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a task to run when it is empty
|
|
*
|
|
* @param whenDone
|
|
* @return
|
|
*/
|
|
public boolean addEmptyTask(final Runnable whenDone) {
|
|
if (this.isEmpty()) {
|
|
// Run
|
|
this.runEmptyTasks();
|
|
if (whenDone != null) {
|
|
whenDone.run();
|
|
}
|
|
return true;
|
|
}
|
|
if (whenDone != null) {
|
|
this.emptyTasks.add(whenDone);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private synchronized boolean runEmptyTasks() {
|
|
if (this.emptyTasks.isEmpty()) {
|
|
return false;
|
|
}
|
|
final ConcurrentLinkedDeque<Runnable> tmp = new ConcurrentLinkedDeque<>(this.emptyTasks);
|
|
this.emptyTasks.clear();
|
|
for (final Runnable runnable : tmp) {
|
|
runnable.run();
|
|
}
|
|
return true;
|
|
}
|
|
}
|