mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2025-01-09 09:57:47 +01:00
parent
c203fda391
commit
7ac4ac696f
8
pom.xml
8
pom.xml
@ -298,14 +298,6 @@
|
|||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- TODO fold BukkitExecutors into ProtocolLib -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.comphenix.executors</groupId>
|
|
||||||
<artifactId>BukkitExecutors</artifactId>
|
|
||||||
<version>1.1-SNAPSHOT</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Testing dependencies -->
|
<!-- Testing dependencies -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
|
@ -33,7 +33,7 @@ import org.bukkit.plugin.Plugin;
|
|||||||
import org.bukkit.plugin.PluginManager;
|
import org.bukkit.plugin.PluginManager;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
import com.comphenix.executors.BukkitExecutors;
|
import com.comphenix.protocol.executors.BukkitExecutors;
|
||||||
import com.comphenix.protocol.async.AsyncFilterManager;
|
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||||
import com.comphenix.protocol.error.BasicErrorReporter;
|
import com.comphenix.protocol.error.BasicErrorReporter;
|
||||||
import com.comphenix.protocol.error.DelegatedErrorReporter;
|
import com.comphenix.protocol.error.DelegatedErrorReporter;
|
||||||
|
@ -0,0 +1,142 @@
|
|||||||
|
package com.comphenix.protocol.executors;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.common.util.concurrent.ListenableScheduledFuture;
|
||||||
|
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
|
||||||
|
abstract class AbstractBukkitService
|
||||||
|
extends AbstractListeningService implements BukkitScheduledExecutorService {
|
||||||
|
|
||||||
|
private static final long MILLISECONDS_PER_TICK = 50;
|
||||||
|
private static final long NANOSECONDS_PER_TICK = 1000000 * MILLISECONDS_PER_TICK;
|
||||||
|
|
||||||
|
private volatile boolean shutdown;
|
||||||
|
private final PendingTasks tasks;
|
||||||
|
|
||||||
|
public AbstractBukkitService(PendingTasks tasks) {
|
||||||
|
this.tasks = tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected <T> RunnableAbstractFuture<T> newTaskFor(Runnable runnable, T value) {
|
||||||
|
return newTaskFor(Executors.callable(runnable, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected <T> RunnableAbstractFuture<T> newTaskFor(final Callable<T> callable) {
|
||||||
|
validateState();
|
||||||
|
return new CallableTask<T>(callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Runnable command) {
|
||||||
|
validateState();
|
||||||
|
|
||||||
|
if (command instanceof RunnableFuture) {
|
||||||
|
tasks.add(getTask(command), (Future<?>) command);
|
||||||
|
} else {
|
||||||
|
// Submit it first
|
||||||
|
submit(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bridge to Bukkit
|
||||||
|
protected abstract BukkitTask getTask(Runnable command);
|
||||||
|
protected abstract BukkitTask getLaterTask(Runnable task, long ticks);
|
||||||
|
protected abstract BukkitTask getTimerTask(long ticksInitial, long ticksDelay, Runnable task);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Runnable> shutdownNow() {
|
||||||
|
shutdown();
|
||||||
|
tasks.cancel();
|
||||||
|
|
||||||
|
// We don't support this
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
shutdown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateState() {
|
||||||
|
if (shutdown) {
|
||||||
|
throw new RejectedExecutionException("Executor service has shut down. Cannot start new tasks.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long toTicks(long delay, TimeUnit unit) {
|
||||||
|
return Math.round(unit.toMillis(delay) / (double)MILLISECONDS_PER_TICK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListenableScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
|
||||||
|
return schedule(Executors.callable(command), delay, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> ListenableScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
|
||||||
|
long ticks = toTicks(delay, unit);
|
||||||
|
|
||||||
|
// Construct future task and Bukkit task
|
||||||
|
CallableTask<V> task = new CallableTask<V>(callable);
|
||||||
|
BukkitTask bukkitTask = getLaterTask(task, ticks);
|
||||||
|
|
||||||
|
tasks.add(bukkitTask, task);
|
||||||
|
return task.getScheduledFuture(System.nanoTime() + delay * NANOSECONDS_PER_TICK, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListenableScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay,
|
||||||
|
long period, TimeUnit unit) {
|
||||||
|
|
||||||
|
long ticksInitial = toTicks(initialDelay, unit);
|
||||||
|
long ticksDelay = toTicks(period, unit);
|
||||||
|
|
||||||
|
// Construct future task and Bukkit task
|
||||||
|
CallableTask<?> task = new CallableTask<Object>(Executors.callable(command)) {
|
||||||
|
protected void compute() {
|
||||||
|
// Do nothing more. This future can only be finished by cancellation
|
||||||
|
try {
|
||||||
|
compute.call();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Let Bukkit handle this
|
||||||
|
throw Throwables.propagate(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
BukkitTask bukkitTask = getTimerTask(ticksInitial, ticksDelay, task);
|
||||||
|
|
||||||
|
tasks.add(bukkitTask, task);
|
||||||
|
return task.getScheduledFuture(
|
||||||
|
System.nanoTime() + ticksInitial * NANOSECONDS_PER_TICK,
|
||||||
|
ticksDelay * NANOSECONDS_PER_TICK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not supported!
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public ListenableScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
|
||||||
|
return scheduleAtFixedRate(command, initialDelay, delay, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
return tasks.awaitTermination(timeout, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShutdown() {
|
||||||
|
return shutdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTerminated() {
|
||||||
|
return tasks.isTerminated();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,288 @@
|
|||||||
|
package com.comphenix.protocol.executors;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is a modified version of
|
||||||
|
* http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/AbstractExecutorService.java?revision=1.35
|
||||||
|
* which contained the following notice:
|
||||||
|
*
|
||||||
|
* Written by Doug Lea with assistance from members of JCP JSR-166 Expert Group and released to the
|
||||||
|
* public domain, as explained at http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
*
|
||||||
|
* Rationale for copying:
|
||||||
|
* Guava targets JDK5, whose AbstractExecutorService class lacks the newTaskFor protected
|
||||||
|
* customization methods needed by MoreExecutors.listeningDecorator. This class is a copy of
|
||||||
|
* AbstractExecutorService from the JSR166 CVS repository. It contains the desired methods.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorCompletionService;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.RunnableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.AbstractFuture;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.ListenableFutureTask;
|
||||||
|
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides default implementations of {@link ListeningExecutorService}
|
||||||
|
* execution methods. This class implements the <tt>submit</tt>,
|
||||||
|
* <tt>invokeAny</tt> and <tt>invokeAll</tt> methods using a
|
||||||
|
* {@link ListenableFutureTask} returned by <tt>newTaskFor</tt>. For example,
|
||||||
|
* the implementation of <tt>submit(Runnable)</tt> creates an associated
|
||||||
|
* <tt>ListenableFutureTask</tt> that is executed and returned.
|
||||||
|
*
|
||||||
|
* @author Doug Lea
|
||||||
|
*/
|
||||||
|
abstract class AbstractListeningService implements ListeningExecutorService {
|
||||||
|
/**
|
||||||
|
* Represents a runnable abstract listenable future task.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public static abstract class RunnableAbstractFuture<T>
|
||||||
|
extends AbstractFuture<T> implements RunnableFuture<T> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a <tt>ListenableFutureTask</tt> for the given runnable and
|
||||||
|
* default value.
|
||||||
|
*
|
||||||
|
* @param runnable - the runnable task being wrapped
|
||||||
|
* @param value - the default value for the returned future
|
||||||
|
* @return a <tt>ListenableFutureTask</tt> which when run will run the
|
||||||
|
* underlying runnable and which, as a <tt>Future</tt>, will yield
|
||||||
|
* the given value as its result and provide for cancellation of the
|
||||||
|
* underlying task.
|
||||||
|
*/
|
||||||
|
protected abstract <T> RunnableAbstractFuture<T> newTaskFor(Runnable runnable, T value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a <tt>ListenableFutureTask</tt> for the given callable task.
|
||||||
|
*
|
||||||
|
* @param callable - the callable task being wrapped
|
||||||
|
* @return a <tt>ListenableFutureTask</tt> which when run will call the
|
||||||
|
* underlying callable and which, as a <tt>Future</tt>, will yield
|
||||||
|
* the callable's result as its result and provide for cancellation
|
||||||
|
* of the underlying task.
|
||||||
|
*/
|
||||||
|
protected abstract <T> RunnableAbstractFuture<T> newTaskFor(Callable<T> callable);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListenableFuture<?> submit(Runnable task) {
|
||||||
|
if (task == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
RunnableAbstractFuture<Void> ftask = newTaskFor(task, null);
|
||||||
|
execute(ftask);
|
||||||
|
return ftask;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> ListenableFuture<T> submit(Runnable task, T result) {
|
||||||
|
if (task == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
RunnableAbstractFuture<T> ftask = newTaskFor(task, result);
|
||||||
|
execute(ftask);
|
||||||
|
return ftask;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> ListenableFuture<T> submit(Callable<T> task) {
|
||||||
|
if (task == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
RunnableAbstractFuture<T> ftask = newTaskFor(task);
|
||||||
|
execute(ftask);
|
||||||
|
return ftask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main mechanics of invokeAny.
|
||||||
|
*/
|
||||||
|
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks, boolean timed, long nanos)
|
||||||
|
throws InterruptedException, ExecutionException, TimeoutException {
|
||||||
|
if (tasks == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
int ntasks = tasks.size();
|
||||||
|
if (ntasks == 0) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
List<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
|
||||||
|
ExecutorCompletionService<T> ecs = new ExecutorCompletionService<T>(this);
|
||||||
|
|
||||||
|
// For efficiency, especially in executors with limited
|
||||||
|
// parallelism, check to see if previously submitted tasks are
|
||||||
|
// done before submitting more of them. This interleaving
|
||||||
|
// plus the exception mechanics account for messiness of main
|
||||||
|
// loop.
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Record exceptions so that if we fail to obtain any
|
||||||
|
// result, we can throw the last exception we got.
|
||||||
|
ExecutionException ee = null;
|
||||||
|
long lastTime = timed ? System.nanoTime() : 0;
|
||||||
|
Iterator<? extends Callable<T>> it = tasks.iterator();
|
||||||
|
|
||||||
|
// Start one task for sure; the rest incrementally
|
||||||
|
futures.add(ecs.submit(it.next()));
|
||||||
|
--ntasks;
|
||||||
|
int active = 1;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
Future<T> f = ecs.poll();
|
||||||
|
if (f == null) {
|
||||||
|
if (ntasks > 0) {
|
||||||
|
--ntasks;
|
||||||
|
futures.add(ecs.submit(it.next()));
|
||||||
|
++active;
|
||||||
|
} else if (active == 0) {
|
||||||
|
break;
|
||||||
|
} else if (timed) {
|
||||||
|
f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
|
||||||
|
if (f == null) {
|
||||||
|
throw new TimeoutException();
|
||||||
|
}
|
||||||
|
long now = System.nanoTime();
|
||||||
|
nanos -= now - lastTime;
|
||||||
|
lastTime = now;
|
||||||
|
} else {
|
||||||
|
f = ecs.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (f != null) {
|
||||||
|
--active;
|
||||||
|
try {
|
||||||
|
return f.get();
|
||||||
|
} catch (ExecutionException eex) {
|
||||||
|
ee = eex;
|
||||||
|
} catch (RuntimeException rex) {
|
||||||
|
ee = new ExecutionException(rex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ee == null) {
|
||||||
|
ee = new ExecutionException(null);
|
||||||
|
}
|
||||||
|
throw ee;
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
for (Future<T> f : futures)
|
||||||
|
f.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException,
|
||||||
|
ExecutionException {
|
||||||
|
try {
|
||||||
|
return doInvokeAny(tasks, false, 0);
|
||||||
|
} catch (TimeoutException cannotHappen) {
|
||||||
|
// assert false;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
|
||||||
|
throws InterruptedException, ExecutionException, TimeoutException {
|
||||||
|
return doInvokeAny(tasks, true, unit.toNanos(timeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
|
||||||
|
throws InterruptedException {
|
||||||
|
if (tasks == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
|
||||||
|
boolean done = false;
|
||||||
|
try {
|
||||||
|
for (Callable<T> t : tasks) {
|
||||||
|
RunnableAbstractFuture<T> f = newTaskFor(t);
|
||||||
|
futures.add(f);
|
||||||
|
execute(f);
|
||||||
|
}
|
||||||
|
for (Future<T> f : futures) {
|
||||||
|
if (!f.isDone()) {
|
||||||
|
try {
|
||||||
|
f.get();
|
||||||
|
} catch (CancellationException | ExecutionException ignore) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done = true;
|
||||||
|
return futures;
|
||||||
|
} finally {
|
||||||
|
if (!done) {
|
||||||
|
for (Future<T> f : futures)
|
||||||
|
f.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout,
|
||||||
|
TimeUnit unit) throws InterruptedException {
|
||||||
|
if (tasks == null || unit == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
long nanos = unit.toNanos(timeout);
|
||||||
|
List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
|
||||||
|
boolean done = false;
|
||||||
|
try {
|
||||||
|
for (Callable<T> t : tasks)
|
||||||
|
futures.add(newTaskFor(t));
|
||||||
|
|
||||||
|
long lastTime = System.nanoTime();
|
||||||
|
|
||||||
|
// Interleave time checks and calls to execute in case
|
||||||
|
// executor doesn't have any/much parallelism.
|
||||||
|
for (Future<T> future : futures) {
|
||||||
|
execute((Runnable) future);
|
||||||
|
long now = System.nanoTime();
|
||||||
|
nanos -= now - lastTime;
|
||||||
|
lastTime = now;
|
||||||
|
if (nanos <= 0) {
|
||||||
|
return futures;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Future<T> f : futures) {
|
||||||
|
if (!f.isDone()) {
|
||||||
|
if (nanos <= 0) {
|
||||||
|
return futures;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
f.get(nanos, TimeUnit.NANOSECONDS);
|
||||||
|
} catch (CancellationException | ExecutionException ignore) {
|
||||||
|
} catch (TimeoutException toe) {
|
||||||
|
return futures;
|
||||||
|
}
|
||||||
|
long now = System.nanoTime();
|
||||||
|
nanos -= now - lastTime;
|
||||||
|
lastTime = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done = true;
|
||||||
|
return futures;
|
||||||
|
} finally {
|
||||||
|
if (!done) {
|
||||||
|
for (Future<T> f : futures)
|
||||||
|
f.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package com.comphenix.protocol.executors;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.bukkit.scheduler.BukkitScheduler;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.executors.AbstractBukkitService;
|
||||||
|
import com.comphenix.protocol.executors.BukkitScheduledExecutorService;
|
||||||
|
import com.comphenix.protocol.executors.PendingTasks;
|
||||||
|
import com.comphenix.protocol.executors.PluginDisabledListener;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
public class BukkitExecutors {
|
||||||
|
private BukkitExecutors() {
|
||||||
|
// Don't make it constructable
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a scheduled executor service for running tasks on the main thread.
|
||||||
|
* @param plugin - plugin that is executing the given tasks.
|
||||||
|
* @return Executor service.
|
||||||
|
*/
|
||||||
|
public static BukkitScheduledExecutorService newSynchronous(final Plugin plugin) {
|
||||||
|
// Bridge destination
|
||||||
|
final BukkitScheduler scheduler = getScheduler(plugin);
|
||||||
|
Preconditions.checkNotNull(plugin, "plugin cannot be NULL");
|
||||||
|
|
||||||
|
BukkitScheduledExecutorService service = new com.comphenix.protocol.executors.AbstractBukkitService(new PendingTasks(plugin, scheduler)) {
|
||||||
|
@Override
|
||||||
|
protected BukkitTask getTask(Runnable command) {
|
||||||
|
return scheduler.runTask(plugin, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BukkitTask getLaterTask(Runnable task, long ticks) {
|
||||||
|
return scheduler.runTaskLater(plugin, task, ticks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BukkitTask getTimerTask(long ticksInitial, long ticksDelay, Runnable task) {
|
||||||
|
return scheduler.runTaskTimer(plugin, task, ticksInitial, ticksDelay);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
PluginDisabledListener.getListener(plugin).addService(service);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a scheduled executor service for running asynchronous tasks.
|
||||||
|
* @param plugin - plugin that is executing the given tasks.
|
||||||
|
* @return Asynchronous executor service.
|
||||||
|
*/
|
||||||
|
public static BukkitScheduledExecutorService newAsynchronous(final Plugin plugin) {
|
||||||
|
// Bridge destination
|
||||||
|
final BukkitScheduler scheduler = getScheduler(plugin);
|
||||||
|
Preconditions.checkNotNull(plugin, "plugin cannot be NULL");
|
||||||
|
|
||||||
|
BukkitScheduledExecutorService service = new com.comphenix.protocol.executors.AbstractBukkitService(new PendingTasks(plugin, scheduler)) {
|
||||||
|
@Override
|
||||||
|
protected BukkitTask getTask(Runnable command) {
|
||||||
|
return scheduler.runTaskAsynchronously(plugin, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BukkitTask getLaterTask(Runnable task, long ticks) {
|
||||||
|
return scheduler.runTaskLaterAsynchronously(plugin, task, ticks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BukkitTask getTimerTask(long ticksInitial, long ticksDelay, Runnable task) {
|
||||||
|
return scheduler.runTaskTimerAsynchronously(plugin, task, ticksInitial, ticksDelay);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
PluginDisabledListener.getListener(plugin).addService(service);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the current Bukkit scheduler.
|
||||||
|
* @return Current scheduler.
|
||||||
|
*/
|
||||||
|
private static BukkitScheduler getScheduler(Plugin plugin) {
|
||||||
|
BukkitScheduler scheduler = plugin.getServer().getScheduler();
|
||||||
|
|
||||||
|
if (scheduler != null) {
|
||||||
|
return scheduler;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unable to retrieve scheduler.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
package com.comphenix.protocol.executors;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
|
|
||||||
|
import org.bukkit.event.*;
|
||||||
|
import org.bukkit.plugin.EventExecutor;
|
||||||
|
import org.bukkit.plugin.IllegalPluginAccessException;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.bukkit.plugin.RegisteredListener;
|
||||||
|
|
||||||
|
public class BukkitFutures {
|
||||||
|
// Represents empty classes
|
||||||
|
private static Listener EMPTY_LISTENER = new Listener() {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a future representing the next invocation of the given event.
|
||||||
|
* @param plugin - owner plugin.
|
||||||
|
* @return Future event invocation.
|
||||||
|
*/
|
||||||
|
public static <TEvent extends Event> ListenableFuture<TEvent> nextEvent(Plugin plugin, Class<TEvent> eventClass) {
|
||||||
|
return BukkitFutures.nextEvent(plugin, eventClass, EventPriority.NORMAL, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a future representing the next invocation of the given event.
|
||||||
|
* @param plugin - owner plugin.
|
||||||
|
* @return Future event invocation.
|
||||||
|
*/
|
||||||
|
public static <TEvent extends Event> ListenableFuture<TEvent> nextEvent(
|
||||||
|
Plugin plugin, Class<TEvent> eventClass, EventPriority priority, boolean ignoreCancelled) {
|
||||||
|
|
||||||
|
// Event and future
|
||||||
|
final HandlerList list = getHandlerList(eventClass);
|
||||||
|
final SettableFuture<TEvent> future = SettableFuture.create();
|
||||||
|
|
||||||
|
EventExecutor executor = new EventExecutor() {
|
||||||
|
private final AtomicBoolean once = new AtomicBoolean();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public void execute(Listener listener, Event event) throws EventException {
|
||||||
|
// Fire the future
|
||||||
|
if (!future.isCancelled() && !once.getAndSet(true)) {
|
||||||
|
future.set((TEvent) event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
RegisteredListener listener = new RegisteredListener(EMPTY_LISTENER, executor, priority, plugin, ignoreCancelled) {
|
||||||
|
@Override
|
||||||
|
public void callEvent(Event event) throws EventException {
|
||||||
|
super.callEvent(event);
|
||||||
|
list.unregister(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure that the future is cleaned up when the plugin is disabled
|
||||||
|
PluginDisabledListener.getListener(plugin).addFuture(future);
|
||||||
|
|
||||||
|
// Add the listener
|
||||||
|
list.register(listener);
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a given event executor.
|
||||||
|
* @param plugin - the owner plugin.
|
||||||
|
* @param eventClass - the event to register.
|
||||||
|
* @param priority - the event priority.
|
||||||
|
* @param executor - the event executor.
|
||||||
|
*/
|
||||||
|
public static void registerEventExecutor(Plugin plugin, Class<? extends Event> eventClass, EventPriority priority, EventExecutor executor) {
|
||||||
|
getHandlerList(eventClass).register(
|
||||||
|
new RegisteredListener(EMPTY_LISTENER, executor, priority, plugin, false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the handler list associated with the given class.
|
||||||
|
* @param clazz - given event class.
|
||||||
|
* @return Associated handler list.
|
||||||
|
*/
|
||||||
|
private static HandlerList getHandlerList(Class<? extends Event> clazz) {
|
||||||
|
// Class must have Event as its superclass
|
||||||
|
while (clazz.getSuperclass() != null && Event.class.isAssignableFrom(clazz.getSuperclass())) {
|
||||||
|
try {
|
||||||
|
Method method = clazz.getDeclaredMethod("getHandlerList");
|
||||||
|
method.setAccessible(true);
|
||||||
|
return (HandlerList) method.invoke(null);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
// Keep on searching
|
||||||
|
clazz = clazz.getSuperclass().asSubclass(Event.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalPluginAccessException(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalPluginAccessException("Unable to find handler list for event "
|
||||||
|
+ clazz.getName());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package com.comphenix.protocol.executors;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ListenableScheduledFuture;
|
||||||
|
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a listening scheduler service that returns {@link ListenableScheduledFuture} instead of {@link ScheduledFuture}.
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public interface BukkitScheduledExecutorService extends ListeningScheduledExecutorService {
|
||||||
|
@Override
|
||||||
|
public ListenableScheduledFuture<?> schedule(
|
||||||
|
Runnable command, long delay, TimeUnit unit);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <V> ListenableScheduledFuture<V> schedule(
|
||||||
|
Callable<V> callable, long delay, TimeUnit unit);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListenableScheduledFuture<?> scheduleAtFixedRate(
|
||||||
|
Runnable command, long initialDelay, long period, TimeUnit unit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is not supported by the underlying Bukkit scheduler.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public ListenableScheduledFuture<?> scheduleWithFixedDelay(
|
||||||
|
Runnable command, long initialDelay, long delay, TimeUnit unit);
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
package com.comphenix.protocol.executors;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.Delayed;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import com.comphenix.protocol.executors.AbstractListeningService.RunnableAbstractFuture;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.util.concurrent.ListenableScheduledFuture;
|
||||||
|
|
||||||
|
class CallableTask<T> extends RunnableAbstractFuture<T> {
|
||||||
|
protected final Callable<T> compute;
|
||||||
|
|
||||||
|
public CallableTask(Callable<T> compute) {
|
||||||
|
Preconditions.checkNotNull(compute, "compute cannot be NULL");
|
||||||
|
|
||||||
|
this.compute = compute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListenableScheduledFuture<T> getScheduledFuture(final long startTime, final long nextDelay) {
|
||||||
|
return new ListenableScheduledFuture<T>() {
|
||||||
|
@Override
|
||||||
|
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||||
|
return CallableTask.this.cancel(mayInterruptIfRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T get() throws InterruptedException, ExecutionException {
|
||||||
|
return CallableTask.this.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T get(long timeout, TimeUnit unit) throws InterruptedException,
|
||||||
|
ExecutionException, TimeoutException {
|
||||||
|
return CallableTask.this.get(timeout, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCancelled() {
|
||||||
|
return CallableTask.this.isCancelled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDone() {
|
||||||
|
return CallableTask.this.isDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addListener(Runnable listener, Executor executor) {
|
||||||
|
CallableTask.this.addListener(listener, executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Delayed o) {
|
||||||
|
return Long.valueOf(getDelay(TimeUnit.NANOSECONDS))
|
||||||
|
.compareTo(o.getDelay(TimeUnit.NANOSECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getDelay(TimeUnit unit) {
|
||||||
|
long current = System.nanoTime();
|
||||||
|
|
||||||
|
// Calculate the correct delay
|
||||||
|
if (current < startTime || !isPeriodic())
|
||||||
|
return unit.convert(startTime - current, TimeUnit.NANOSECONDS);
|
||||||
|
else
|
||||||
|
return unit.convert(((current - startTime) % nextDelay), TimeUnit.NANOSECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Override
|
||||||
|
public boolean isPeriodic() {
|
||||||
|
return nextDelay > 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked by the thread responsible for computing this future.
|
||||||
|
*/
|
||||||
|
protected void compute() {
|
||||||
|
try {
|
||||||
|
// Save result
|
||||||
|
if (!isCancelled()) {
|
||||||
|
set(compute.call());
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
setException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
compute();
|
||||||
|
}
|
||||||
|
}
|
140
src/main/java/com/comphenix/protocol/executors/PendingTasks.java
Normal file
140
src/main/java/com/comphenix/protocol/executors/PendingTasks.java
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package com.comphenix.protocol.executors;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.bukkit.scheduler.BukkitScheduler;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
|
||||||
|
class PendingTasks {
|
||||||
|
/**
|
||||||
|
* Represents a wrapper for a cancelable task.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
private interface CancelableFuture {
|
||||||
|
void cancel();
|
||||||
|
boolean isTaskCancelled();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every pending task
|
||||||
|
private final Set<CancelableFuture> pending = new HashSet<>();
|
||||||
|
private final Object pendingLock = new Object();
|
||||||
|
|
||||||
|
// Handle arbitrary cancelation
|
||||||
|
private final Plugin plugin;
|
||||||
|
private final BukkitScheduler scheduler;
|
||||||
|
private BukkitTask cancellationTask;
|
||||||
|
|
||||||
|
public PendingTasks(Plugin plugin, BukkitScheduler scheduler) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.scheduler = scheduler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(final BukkitTask task, final Future<?> future) {
|
||||||
|
add(new CancelableFuture() {
|
||||||
|
@Override
|
||||||
|
public boolean isTaskCancelled() {
|
||||||
|
// If completed, check its cancellation state
|
||||||
|
if (future.isDone())
|
||||||
|
return future.isCancelled();
|
||||||
|
|
||||||
|
return !(scheduler.isCurrentlyRunning(task.getTaskId()) ||
|
||||||
|
scheduler.isQueued(task.getTaskId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel() {
|
||||||
|
// Make sure
|
||||||
|
task.cancel();
|
||||||
|
future.cancel(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private CancelableFuture add(CancelableFuture task) {
|
||||||
|
synchronized (pendingLock) {
|
||||||
|
pending.add(task);
|
||||||
|
pendingLock.notifyAll();
|
||||||
|
beginCancellationTask();
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void beginCancellationTask() {
|
||||||
|
if (cancellationTask == null) {
|
||||||
|
cancellationTask = scheduler.runTaskTimer(plugin, () -> {
|
||||||
|
// Check for cancellations
|
||||||
|
synchronized (pendingLock) {
|
||||||
|
boolean changed = false;
|
||||||
|
|
||||||
|
for (Iterator<CancelableFuture> it = pending.iterator(); it.hasNext(); ) {
|
||||||
|
CancelableFuture future = it.next();
|
||||||
|
|
||||||
|
// Remove cancelled tasks
|
||||||
|
if (future.isTaskCancelled()) {
|
||||||
|
future.cancel();
|
||||||
|
it.remove();
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify waiting threads
|
||||||
|
if (changed) {
|
||||||
|
pendingLock.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop if we are out of tasks
|
||||||
|
if (isTerminated()) {
|
||||||
|
cancellationTask.cancel();
|
||||||
|
cancellationTask = null;
|
||||||
|
}
|
||||||
|
}, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel all pending tasks.
|
||||||
|
*/
|
||||||
|
public void cancel() {
|
||||||
|
for (CancelableFuture task : pending) {
|
||||||
|
task.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait until all pending tasks have completed.
|
||||||
|
* @param timeout - the current timeout.
|
||||||
|
* @param unit - unit of the timeout.
|
||||||
|
* @return TRUE if every pending task has terminated, FALSE if we reached the timeout.
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
|
long expire = System.nanoTime() + unit.toNanos(timeout);
|
||||||
|
|
||||||
|
synchronized (pendingLock) {
|
||||||
|
// Wait until the tasks have all terminated
|
||||||
|
while (!isTerminated()) {
|
||||||
|
// Check timeout
|
||||||
|
if (expire < System.nanoTime())
|
||||||
|
return false;
|
||||||
|
unit.timedWait(pendingLock, timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Timeout!
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if all tasks have completed executing.
|
||||||
|
* @return TRUE if they have, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isTerminated() {
|
||||||
|
return pending.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,134 @@
|
|||||||
|
package com.comphenix.protocol.executors;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
import com.google.common.collect.MapMaker;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.server.PluginDisableEvent;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
class PluginDisabledListener implements Listener {
|
||||||
|
private static final ConcurrentMap<Plugin, PluginDisabledListener> LISTENERS = new MapMaker().weakKeys().makeMap();
|
||||||
|
|
||||||
|
// Objects that must be disabled
|
||||||
|
private final Set<Future<?>> futures = Collections.newSetFromMap(new WeakHashMap<>());
|
||||||
|
private final Set<ExecutorService> services = Collections.newSetFromMap(new WeakHashMap<>());
|
||||||
|
private final Object setLock = new Object();
|
||||||
|
|
||||||
|
// The plugin we're looking for
|
||||||
|
private final Plugin plugin;
|
||||||
|
private boolean disabled;
|
||||||
|
|
||||||
|
private PluginDisabledListener(Plugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the associated disabled listener.
|
||||||
|
* @param plugin - the plugin.
|
||||||
|
* @return Associated listener.
|
||||||
|
*/
|
||||||
|
public static PluginDisabledListener getListener(final Plugin plugin) {
|
||||||
|
PluginDisabledListener result = LISTENERS.get(plugin);
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
final PluginDisabledListener created = new PluginDisabledListener(plugin);
|
||||||
|
result = LISTENERS.putIfAbsent(plugin, created);
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
// Register listener - we can't use the normal method as the plugin might not be enabled yet
|
||||||
|
BukkitFutures.registerEventExecutor(plugin, PluginDisableEvent.class, EventPriority.NORMAL,
|
||||||
|
(listener, event) -> {
|
||||||
|
if (event instanceof PluginDisableEvent) {
|
||||||
|
created.onPluginDisabled((PluginDisableEvent) event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
result = created;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the given future will be cancelled when the plugin is disabled.
|
||||||
|
* @param future - the future to cancel.
|
||||||
|
*/
|
||||||
|
public void addFuture(final ListenableFuture<?> future) {
|
||||||
|
synchronized (setLock) {
|
||||||
|
if (disabled) {
|
||||||
|
processFuture(future);
|
||||||
|
} else {
|
||||||
|
futures.add(future);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the future when it has computed
|
||||||
|
Futures.addCallback(future, new FutureCallback<Object>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object value) {
|
||||||
|
synchronized (setLock) {
|
||||||
|
futures.remove(future);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable ex) {
|
||||||
|
synchronized (setLock) {
|
||||||
|
futures.remove(future);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that a given service is shutdown when the plugin is disabled.
|
||||||
|
* @param service - the service.
|
||||||
|
*/
|
||||||
|
public void addService(ExecutorService service) {
|
||||||
|
synchronized (setLock) {
|
||||||
|
if (disabled) {
|
||||||
|
processService(service);
|
||||||
|
} else {
|
||||||
|
services.add(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Will be registered manually
|
||||||
|
public void onPluginDisabled(PluginDisableEvent e) {
|
||||||
|
if (e.getPlugin().equals(plugin)) {
|
||||||
|
synchronized (setLock) {
|
||||||
|
disabled = true;
|
||||||
|
|
||||||
|
// Cancel all unfinished futures
|
||||||
|
for (Future<?> future : futures) {
|
||||||
|
processFuture(future);
|
||||||
|
}
|
||||||
|
for (ExecutorService service : services) {
|
||||||
|
processService(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processFuture(Future<?> future) {
|
||||||
|
if (!future.isDone()) {
|
||||||
|
future.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processService(ExecutorService service) {
|
||||||
|
service.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@ import org.bukkit.Server;
|
|||||||
import org.bukkit.event.world.WorldInitEvent;
|
import org.bukkit.event.world.WorldInitEvent;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
import com.comphenix.executors.BukkitFutures;
|
import com.comphenix.protocol.executors.BukkitFutures;
|
||||||
import com.comphenix.protocol.async.AsyncFilterManager;
|
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||||
import com.comphenix.protocol.error.ErrorReporter;
|
import com.comphenix.protocol.error.ErrorReporter;
|
||||||
import com.comphenix.protocol.error.Report;
|
import com.comphenix.protocol.error.Report;
|
||||||
|
Loading…
Reference in New Issue
Block a user