diff --git a/scripts/inst.sh b/scripts/inst.sh
index 989dc3350..6bbc40d14 100644
--- a/scripts/inst.sh
+++ b/scripts/inst.sh
@@ -5,7 +5,7 @@ set -e
basedir="$(pwd -P)"
version="master"
-(git submodule update --init --remote && cd "work/Paper" && git checkout "$version" && cd "$basedir" && chmod +x scripts/build.sh && ./scripts/build.sh "$basedir" "$1" "$2" "$3") || (
+(git submodule update --init --remote && cd "$basedir" && chmod +x scripts/build.sh && ./scripts/build.sh "$basedir" "$1" "$2" "$3") || (
echo "Failed to build Akarin"
exit 1
) || exit 1
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 38fdc5d20..623baad91 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -174,7 +174,7 @@ public final class CraftServer implements Server {
private final String bukkitVersion = Versioning.getBukkitVersion();
private final Logger logger = Logger.getLogger("Minecraft");
private final ServicesManager servicesManager = new SimpleServicesManager();
- private final CraftScheduler scheduler = new CraftScheduler();
+ private final CraftScheduler scheduler = new org.bukkit.craftbukkit.scheduler.CraftSyncScheduler(); // Akarin
private final CraftCommandMap commandMap = new CraftCommandMap(this);
private final SimpleHelpMap helpMap = new SimpleHelpMap(this);
private final StandardMessenger messenger = new StandardMessenger();
diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftSyncScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftSyncScheduler.java
new file mode 100644
index 000000000..c23c6d1a5
--- /dev/null
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftSyncScheduler.java
@@ -0,0 +1,621 @@
+package org.bukkit.craftbukkit.scheduler;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.PriorityQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.logging.Level;
+
+import co.aikar.timings.MinecraftTimings; // Paper
+import com.destroystokyo.paper.event.server.ServerExceptionEvent;
+import com.destroystokyo.paper.exception.ServerSchedulerException;
+import org.apache.commons.lang.Validate;
+import org.bukkit.plugin.IllegalPluginAccessException;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.scheduler.BukkitRunnable;
+import org.bukkit.scheduler.BukkitScheduler;
+import org.bukkit.scheduler.BukkitTask;
+import org.bukkit.scheduler.BukkitWorker;
+
+/**
+ * The fundamental concepts for this implementation:
+ *
Main thread owns {@link #head} and {@link #currentTick}, but it may be read from any thread
+ * Main thread exclusively controls {@link #temp} and {@link #pending}.
+ * They are never to be accessed outside of the main thread; alternatives exist to prevent locking.
+ * {@link #head} to {@link #tail} act as a linked list/queue, with 1 consumer and infinite producers.
+ * Adding to the tail is atomic and very efficient; utility method is {@link #handle(CraftTask, long)} or {@link #addTask(CraftTask)}.
+ * Changing the period on a task is delicate.
+ * Any future task needs to notify waiting threads.
+ * Async tasks must be synchronized to make sure that any thread that's finishing will remove itself from {@link #runners}.
+ * Another utility method is provided for this, {@link #cancelTask(int)}
+ * {@link #runners} provides a moderately up-to-date view of active tasks.
+ * If the linked head to tail set is read, all remaining tasks that were active at the time execution started will be located in runners.
+ * Async tasks are responsible for removing themselves from runners
+ * Sync tasks are only to be removed from runners on the main thread when coupled with a removal from pending and temp.
+ * Most of the design in this scheduler relies on queuing special tasks to perform any data changes on the main thread.
+ * When executed from inside a synchronous method, the scheduler will be updated before next execution by virtue of the frequent {@link #parsePending()} calls.
+ */
+public class CraftSyncScheduler extends CraftScheduler implements BukkitScheduler { // Akarin - fake extends, override all things
+
+ /**
+ * Counter for IDs. Order doesn't matter, only uniqueness.
+ */
+ private int ids = 1; // Akarin - AtomicInteger -> int
+ /**
+ * Current head of linked-list. This reference is always stale, {@link CraftTask#next} is the live reference.
+ */
+ private CraftTask head = new CraftTask(); // Akarin - remove volatile
+ /**
+ * Tail of a linked-list. AtomicReference only matters when adding to queue
+ */
+ private CraftTask tail = new CraftTask(head); // Akarin - remove AtomicReference
+ /**
+ * Main thread logic only
+ */
+ final PriorityQueue pending = new PriorityQueue(10, // Paper
+ new Comparator() {
+ public int compare(final CraftTask o1, final CraftTask o2) {
+ int value = Long.compare(o1.getNextRun(), o2.getNextRun());
+
+ // If the tasks should run on the same tick they should be run FIFO
+ return value != 0 ? value : Integer.compare(o1.getTaskId(), o2.getTaskId());
+ }
+ });
+ /**
+ * Main thread logic only
+ */
+ private final List temp = new ArrayList();
+ /**
+ * These are tasks that are currently active. It's provided for 'viewing' the current state.
+ */
+ final java.util.HashMap runners = new java.util.HashMap(); // Paper // Akarin - ConcurrentHashMap -> HashMap
+ /**
+ * The sync task that is currently running on the main thread.
+ */
+ private CraftTask currentTask = null; // Akarin - remove volatile
+ int currentTick = -1; // Paper // Akarin - remove volatile
+ //private final Executor executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %d").build()); // Paper - moved to AsyncScheduler
+ //private CraftAsyncDebugger debugHead = new CraftAsyncDebugger(-1, null, null) {@Override StringBuilder debugTo(StringBuilder string) {return string;}}; // Paper
+ //private CraftAsyncDebugger debugTail = debugHead; // Paper
+ private static final int RECENT_TICKS;
+
+ static {
+ RECENT_TICKS = 30;
+ }
+
+
+ // Paper start
+ private final CraftScheduler asyncScheduler = new CraftAsyncScheduler(); // Akarin
+ private final boolean isAsyncScheduler = false; // Akarin
+ public CraftSyncScheduler() {
+ this(false);
+ }
+
+ public CraftSyncScheduler(boolean isAsync) {
+ // Akarin start
+ assert !isAsync : "Unexpected Async Scheduler. Report this to Akarin";
+ //this.isAsyncScheduler = isAsync;
+ //if (isAsync) {
+ // this.asyncScheduler = new CraftAsyncScheduler();
+ //} else {
+ // this.asyncScheduler = new CraftAsyncScheduler();
+ //}
+ // Akarin end
+ }
+ // Paper end
+ @Override
+ public int scheduleSyncDelayedTask(final Plugin plugin, final Runnable task) {
+ return this.scheduleSyncDelayedTask(plugin, task, 0L);
+ }
+
+ @Override
+ public BukkitTask runTask(Plugin plugin, Runnable runnable) {
+ return runTaskLater(plugin, runnable, 0L);
+ }
+
+ @Override
+ public void runTask(Plugin plugin, Consumer task) throws IllegalArgumentException {
+ runTaskLater(plugin, task, 0L);
+ }
+
+ @Deprecated
+ @Override
+ public int scheduleAsyncDelayedTask(final Plugin plugin, final Runnable task) {
+ return this.scheduleAsyncDelayedTask(plugin, task, 0L);
+ }
+
+ @Override
+ public BukkitTask runTaskAsynchronously(Plugin plugin, Runnable runnable) {
+ return runTaskLaterAsynchronously(plugin, runnable, 0L);
+ }
+
+ @Override
+ public void runTaskAsynchronously(Plugin plugin, Consumer task) throws IllegalArgumentException {
+ runTaskLaterAsynchronously(plugin, task, 0L);
+ }
+
+ @Override
+ public int scheduleSyncDelayedTask(final Plugin plugin, final Runnable task, final long delay) {
+ return this.scheduleSyncRepeatingTask(plugin, task, delay, CraftTask.NO_REPEATING);
+ }
+
+ @Override
+ public BukkitTask runTaskLater(Plugin plugin, Runnable runnable, long delay) {
+ return runTaskTimer(plugin, runnable, delay, CraftTask.NO_REPEATING);
+ }
+
+ @Override
+ public void runTaskLater(Plugin plugin, Consumer task, long delay) throws IllegalArgumentException {
+ runTaskTimer(plugin, task, delay, CraftTask.NO_REPEATING);
+ }
+
+ @Deprecated
+ @Override
+ public int scheduleAsyncDelayedTask(final Plugin plugin, final Runnable task, final long delay) {
+ return this.scheduleAsyncRepeatingTask(plugin, task, delay, CraftTask.NO_REPEATING);
+ }
+
+ @Override
+ public BukkitTask runTaskLaterAsynchronously(Plugin plugin, Runnable runnable, long delay) {
+ return runTaskTimerAsynchronously(plugin, runnable, delay, CraftTask.NO_REPEATING);
+ }
+
+ @Override
+ public void runTaskLaterAsynchronously(Plugin plugin, Consumer task, long delay) throws IllegalArgumentException {
+ runTaskTimerAsynchronously(plugin, task, delay, CraftTask.NO_REPEATING);
+ }
+
+ @Override
+ public void runTaskTimerAsynchronously(Plugin plugin, Consumer task, long delay, long period) throws IllegalArgumentException {
+ runTaskTimerAsynchronously(plugin, (Object) task, delay, CraftTask.NO_REPEATING);
+ }
+
+ @Override
+ public int scheduleSyncRepeatingTask(final Plugin plugin, final Runnable runnable, long delay, long period) {
+ return runTaskTimer(plugin, runnable, delay, period).getTaskId();
+ }
+
+ @Override
+ public BukkitTask runTaskTimer(Plugin plugin, Runnable runnable, long delay, long period) {
+ return runTaskTimer(plugin, (Object) runnable, delay, period);
+ }
+
+ @Override
+ public void runTaskTimer(Plugin plugin, Consumer task, long delay, long period) throws IllegalArgumentException {
+ runTaskTimer(plugin, (Object) task, delay, period);
+ }
+
+ public BukkitTask runTaskTimer(Plugin plugin, Object runnable, long delay, long period) {
+ validate(plugin, runnable);
+ if (delay < 0L) {
+ delay = 0;
+ }
+ if (period == CraftTask.ERROR) {
+ period = 1L;
+ } else if (period < CraftTask.NO_REPEATING) {
+ period = CraftTask.NO_REPEATING;
+ }
+ return handle(new CraftTask(plugin, runnable, nextId(), period), delay);
+ }
+
+ @Deprecated
+ @Override
+ public int scheduleAsyncRepeatingTask(final Plugin plugin, final Runnable runnable, long delay, long period) {
+ return runTaskTimerAsynchronously(plugin, runnable, delay, period).getTaskId();
+ }
+
+ @Override
+ public BukkitTask runTaskTimerAsynchronously(Plugin plugin, Runnable runnable, long delay, long period) {
+ return runTaskTimerAsynchronously(plugin, (Object) runnable, delay, period);
+ }
+
+ public BukkitTask runTaskTimerAsynchronously(Plugin plugin, Object runnable, long delay, long period) {
+ validate(plugin, runnable);
+ if (delay < 0L) {
+ delay = 0;
+ }
+ if (period == CraftTask.ERROR) {
+ period = 1L;
+ } else if (period < CraftTask.NO_REPEATING) {
+ period = CraftTask.NO_REPEATING;
+ }
+ return handle(new CraftAsyncTask(this.asyncScheduler.runners, plugin, runnable, nextId(), period), delay); // Paper
+ }
+
+ @Override
+ public Future callSyncMethod(final Plugin plugin, final Callable task) {
+ validate(plugin, task);
+ final CraftFuture future = new CraftFuture(task, plugin, nextId());
+ handle(future, 0L);
+ return future;
+ }
+
+ @Override
+ public void cancelTask(final int taskId) {
+ if (taskId <= 0) {
+ return;
+ }
+ // Paper start
+ if (!this.isAsyncScheduler) {
+ this.asyncScheduler.cancelTask(taskId);
+ }
+ // Paper end
+ CraftTask task = runners.get(taskId);
+ if (task != null) {
+ task.cancel0();
+ }
+ task = new CraftTask(
+ new Runnable() {
+ public void run() {
+ if (!check(CraftSyncScheduler.this.temp)) {
+ check(CraftSyncScheduler.this.pending);
+ }
+ }
+ private boolean check(final Iterable collection) {
+ final Iterator tasks = collection.iterator();
+ while (tasks.hasNext()) {
+ final CraftTask task = tasks.next();
+ if (task.getTaskId() == taskId) {
+ task.cancel0();
+ tasks.remove();
+ if (task.isSync()) {
+ runners.remove(taskId);
+ }
+ return true;
+ }
+ }
+ return false;
+ }}){{this.timings=co.aikar.timings.MinecraftTimings.getCancelTasksTimer();}}; // Paper
+ handle(task, 0L);
+ for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) {
+ if (taskPending == task) {
+ return;
+ }
+ if (taskPending.getTaskId() == taskId) {
+ taskPending.cancel0();
+ }
+ }
+ }
+
+ @Override
+ public void cancelTasks(final Plugin plugin) {
+ Validate.notNull(plugin, "Cannot cancel tasks of null plugin");
+ // Paper start
+ if (!this.isAsyncScheduler) {
+ this.asyncScheduler.cancelTasks(plugin);
+ }
+ // Paper end
+ final CraftTask task = new CraftTask(
+ new Runnable() {
+ public void run() {
+ check(CraftSyncScheduler.this.pending);
+ check(CraftSyncScheduler.this.temp);
+ }
+ void check(final Iterable collection) {
+ final Iterator tasks = collection.iterator();
+ while (tasks.hasNext()) {
+ final CraftTask task = tasks.next();
+ if (task.getOwner().equals(plugin)) {
+ task.cancel0();
+ tasks.remove();
+ if (task.isSync()) {
+ runners.remove(task.getTaskId());
+ }
+ }
+ }
+ }
+ }){{this.timings=co.aikar.timings.MinecraftTimings.getCancelTasksTimer(plugin);}}; // Paper
+ handle(task, 0L);
+ for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) {
+ if (taskPending == task) {
+ break;
+ }
+ if (taskPending.getTaskId() != -1 && taskPending.getOwner().equals(plugin)) {
+ taskPending.cancel0();
+ }
+ }
+ for (CraftTask runner : runners.values()) {
+ if (runner.getOwner().equals(plugin)) {
+ runner.cancel0();
+ }
+ }
+ }
+
+ @Override
+ public boolean isCurrentlyRunning(final int taskId) {
+ // Paper start
+ if (!isAsyncScheduler) {
+ if (this.asyncScheduler.isCurrentlyRunning(taskId)) {
+ return true;
+ }
+ }
+ // Paper end
+ final CraftTask task = runners.get(taskId);
+ if (task == null) {
+ return false;
+ }
+ if (task.isSync()) {
+ return (task == currentTask);
+ }
+ final CraftAsyncTask asyncTask = (CraftAsyncTask) task;
+ synchronized (asyncTask.getWorkers()) {
+ return !asyncTask.getWorkers().isEmpty();
+ }
+ }
+
+ @Override
+ public boolean isQueued(final int taskId) {
+ if (taskId <= 0) {
+ return false;
+ }
+ // Paper start
+ if (!this.isAsyncScheduler && this.asyncScheduler.isQueued(taskId)) {
+ return true;
+ }
+ // Paper end
+ for (CraftTask task = head.getNext(); task != null; task = task.getNext()) {
+ if (task.getTaskId() == taskId) {
+ return task.getPeriod() >= CraftTask.NO_REPEATING; // The task will run
+ }
+ }
+ CraftTask task = runners.get(taskId);
+ return task != null && task.getPeriod() >= CraftTask.NO_REPEATING;
+ }
+
+ @Override
+ public List getActiveWorkers() {
+ // Paper start
+ if (!isAsyncScheduler) {
+ //noinspection TailRecursion
+ return this.asyncScheduler.getActiveWorkers();
+ }
+ // Paper end
+ final ArrayList workers = new ArrayList();
+ for (final CraftTask taskObj : runners.values()) {
+ // Iterator will be a best-effort (may fail to grab very new values) if called from an async thread
+ if (taskObj.isSync()) {
+ continue;
+ }
+ final CraftAsyncTask task = (CraftAsyncTask) taskObj;
+ synchronized (task.getWorkers()) {
+ // This will never have an issue with stale threads; it's state-safe
+ workers.addAll(task.getWorkers());
+ }
+ }
+ return workers;
+ }
+
+ @Override
+ public List getPendingTasks() {
+ final ArrayList truePending = new ArrayList();
+ for (CraftTask task = head.getNext(); task != null; task = task.getNext()) {
+ if (task.getTaskId() != -1) {
+ // -1 is special code
+ truePending.add(task);
+ }
+ }
+
+ final ArrayList pending = new ArrayList();
+ for (CraftTask task : runners.values()) {
+ if (task.getPeriod() >= CraftTask.NO_REPEATING) {
+ pending.add(task);
+ }
+ }
+
+ for (final CraftTask task : truePending) {
+ if (task.getPeriod() >= CraftTask.NO_REPEATING && !pending.contains(task)) {
+ pending.add(task);
+ }
+ }
+ // Paper start
+ if (!this.isAsyncScheduler) {
+ pending.addAll(this.asyncScheduler.getPendingTasks());
+ }
+ // Paper end
+ return pending;
+ }
+
+ /**
+ * This method is designed to never block or wait for locks; an immediate execution of all current tasks.
+ */
+ public void mainThreadHeartbeat(final int currentTick) {
+ // Paper start
+ if (!this.isAsyncScheduler) {
+ this.asyncScheduler.mainThreadHeartbeat(currentTick);
+ }
+ // Paper end
+ this.currentTick = currentTick;
+ final List temp = this.temp;
+ parsePending();
+ while (isReady(currentTick)) {
+ final CraftTask task = pending.remove();
+ if (task.getPeriod() < CraftTask.NO_REPEATING) {
+ if (task.isSync()) {
+ runners.remove(task.getTaskId(), task);
+ }
+ parsePending();
+ continue;
+ }
+ if (task.isSync()) {
+ currentTask = task;
+ try {
+ task.run();
+ } catch (final Throwable throwable) {
+ // Paper start
+ String msg = String.format(
+ "Task #%s for %s generated an exception",
+ task.getTaskId(),
+ task.getOwner().getDescription().getFullName());
+ task.getOwner().getLogger().log(
+ Level.WARNING,
+ msg,
+ throwable);
+ task.getOwner().getServer().getPluginManager().callEvent(
+ new ServerExceptionEvent(new ServerSchedulerException(msg, throwable, task))
+ );
+ // Paper end
+ } finally {
+ currentTask = null;
+ }
+ parsePending();
+ } else {
+ //debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass())); // Paper
+ task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Paper"); // Paper
+ // We don't need to parse pending
+ // (async tasks must live with race-conditions if they attempt to cancel between these few lines of code)
+ }
+ final long period = task.getPeriod(); // State consistency
+ if (period > 0) {
+ task.setNextRun(currentTick + period);
+ temp.add(task);
+ } else if (task.isSync()) {
+ runners.remove(task.getTaskId());
+ }
+ }
+ MinecraftTimings.bukkitSchedulerFinishTimer.startTiming();
+ pending.addAll(temp);
+ temp.clear();
+ MinecraftTimings.bukkitSchedulerFinishTimer.stopTiming();
+ //debugHead = debugHead.getNextHead(currentTick); // Paper
+ }
+
+ protected void addTask(final CraftTask task) {
+ // Akarin start
+ //final AtomicReference tail = this.tail;
+ //CraftTask tailTask = tail.get();
+ //while (!tail.compareAndSet(tailTask, task)) {
+ // tailTask = tail.get();
+ //}
+ (tail = task).setNext(task);
+ // Akarin end
+ }
+
+ protected CraftTask handle(final CraftTask task, final long delay) { // Paper
+ // Paper start
+ if (!this.isAsyncScheduler && !task.isSync()) {
+ this.asyncScheduler.handle(task, delay);
+ return task;
+ }
+ // Paper end
+ task.setNextRun(currentTick + delay);
+ addTask(task);
+ return task;
+ }
+
+ private static void validate(final Plugin plugin, final Object task) {
+ Validate.notNull(plugin, "Plugin cannot be null");
+ Validate.notNull(task, "Task cannot be null");
+ Validate.isTrue(task instanceof Runnable || task instanceof Consumer || task instanceof Callable, "Task must be Runnable, Consumer, or Callable");
+ if (!plugin.isEnabled()) {
+ throw new IllegalPluginAccessException("Plugin attempted to register task while disabled");
+ }
+ }
+
+ private int nextId() {
+ return ids++; // Akarin
+ }
+
+ void parsePending() { // Paper
+ if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.startTiming(); // Paper
+ CraftTask head = this.head;
+ CraftTask task = head.getNext();
+ CraftTask lastTask = head;
+ for (; task != null; task = (lastTask = task).getNext()) {
+ if (task.getTaskId() == -1) {
+ task.run();
+ } else if (task.getPeriod() >= CraftTask.NO_REPEATING) {
+ pending.add(task);
+ runners.put(task.getTaskId(), task);
+ }
+ }
+ // We split this because of the way things are ordered for all of the async calls in CraftScheduler
+ // (it prevents race-conditions)
+ for (task = head; task != lastTask; task = head) {
+ head = task.getNext();
+ task.setNext(null);
+ }
+ this.head = lastTask;
+ if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.stopTiming(); // Paper
+ }
+
+ private boolean isReady(final int currentTick) {
+ return !pending.isEmpty() && pending.peek().getNextRun() <= currentTick;
+ }
+
+ @Override
+ public String toString() {
+ // Paper start
+ return "";
+ /*
+ int debugTick = currentTick;
+ StringBuilder string = new StringBuilder("Recent tasks from ").append(debugTick - RECENT_TICKS).append('-').append(debugTick).append('{');
+ debugHead.debugTo(string);
+ return string.append('}').toString();
+ */
+ // Paper end
+ }
+
+ @Deprecated
+ @Override
+ public int scheduleSyncDelayedTask(Plugin plugin, BukkitRunnable task, long delay) {
+ return scheduleSyncDelayedTask(plugin, (Runnable) task, delay);
+ }
+
+ @Deprecated
+ @Override
+ public int scheduleSyncDelayedTask(Plugin plugin, BukkitRunnable task) {
+ return scheduleSyncDelayedTask(plugin, (Runnable) task);
+ }
+
+ @Deprecated
+ @Override
+ public int scheduleSyncRepeatingTask(Plugin plugin, BukkitRunnable task, long delay, long period) {
+ return scheduleSyncRepeatingTask(plugin, (Runnable) task, delay, period);
+ }
+
+ @Deprecated
+ @Override
+ public BukkitTask runTask(Plugin plugin, BukkitRunnable task) throws IllegalArgumentException {
+ return runTask(plugin, (Runnable) task);
+ }
+
+ @Deprecated
+ @Override
+ public BukkitTask runTaskAsynchronously(Plugin plugin, BukkitRunnable task) throws IllegalArgumentException {
+ return runTaskAsynchronously(plugin, (Runnable) task);
+ }
+
+ @Deprecated
+ @Override
+ public BukkitTask runTaskLater(Plugin plugin, BukkitRunnable task, long delay) throws IllegalArgumentException {
+ return runTaskLater(plugin, (Runnable) task, delay);
+ }
+
+ @Deprecated
+ @Override
+ public BukkitTask runTaskLaterAsynchronously(Plugin plugin, BukkitRunnable task, long delay) throws IllegalArgumentException {
+ return runTaskLaterAsynchronously(plugin, (Runnable) task, delay);
+ }
+
+ @Deprecated
+ @Override
+ public BukkitTask runTaskTimer(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException {
+ return runTaskTimer(plugin, (Runnable) task, delay, period);
+ }
+
+ @Deprecated
+ @Override
+ public BukkitTask runTaskTimerAsynchronously(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException {
+ return runTaskTimerAsynchronously(plugin, (Runnable) task, delay, period);
+ }
+}