From 284be61899c0c55eeeea53c3f38072b06f6bef5d Mon Sep 17 00:00:00 2001 From: George V Date: Thu, 3 Nov 2022 18:49:52 +0200 Subject: [PATCH] feat(Scheduler): Bukkit Scheduler for java applications --- .../api/scheduler/AsyncDebugger.java | 35 ++ .../georgev22/api/scheduler/AsyncTask.java | 112 ++++ .../com/georgev22/api/scheduler/Future.java | 103 ++++ .../georgev22/api/scheduler/Scheduler.java | 549 ++++++++++++++++++ .../api/scheduler/SchedulerManager.java | 10 + .../api/scheduler/SchedulerRunnable.java | 170 ++++++ .../com/georgev22/api/scheduler/Task.java | 133 +++++ .../api/scheduler/interfaces/Scheduler.java | 448 ++++++++++++++ .../api/scheduler/interfaces/Task.java | 42 ++ .../api/scheduler/interfaces/Worker.java | 34 ++ 10 files changed, 1636 insertions(+) create mode 100644 src/main/java/com/georgev22/api/scheduler/AsyncDebugger.java create mode 100644 src/main/java/com/georgev22/api/scheduler/AsyncTask.java create mode 100644 src/main/java/com/georgev22/api/scheduler/Future.java create mode 100644 src/main/java/com/georgev22/api/scheduler/Scheduler.java create mode 100644 src/main/java/com/georgev22/api/scheduler/SchedulerManager.java create mode 100644 src/main/java/com/georgev22/api/scheduler/SchedulerRunnable.java create mode 100644 src/main/java/com/georgev22/api/scheduler/Task.java create mode 100644 src/main/java/com/georgev22/api/scheduler/interfaces/Scheduler.java create mode 100644 src/main/java/com/georgev22/api/scheduler/interfaces/Task.java create mode 100644 src/main/java/com/georgev22/api/scheduler/interfaces/Worker.java diff --git a/src/main/java/com/georgev22/api/scheduler/AsyncDebugger.java b/src/main/java/com/georgev22/api/scheduler/AsyncDebugger.java new file mode 100644 index 0000000..eef6f4a --- /dev/null +++ b/src/main/java/com/georgev22/api/scheduler/AsyncDebugger.java @@ -0,0 +1,35 @@ +package com.georgev22.api.scheduler; + + +class AsyncDebugger { + private AsyncDebugger next = null; + private final int expiry; + private final Class clazzT; + private final Class clazz; + + AsyncDebugger(final int expiry, final Class clazzT, final Class clazz) { + this.expiry = expiry; + this.clazzT = clazzT; + this.clazz = clazz; + + } + + final AsyncDebugger getNextHead(final int time) { + AsyncDebugger next, current = this; + while (time > current.expiry && (next = current.next) != null) { + current = next; + } + return current; + } + + final AsyncDebugger setNext(final AsyncDebugger next) { + return this.next = next; + } + + StringBuilder debugTo(final StringBuilder string) { + for (AsyncDebugger next = this; next != null; next = next.next) { + string.append(next.clazzT.getSimpleName()).append(':').append(next.clazz.getName()).append('@').append(next.expiry).append(','); + } + return string; + } +} diff --git a/src/main/java/com/georgev22/api/scheduler/AsyncTask.java b/src/main/java/com/georgev22/api/scheduler/AsyncTask.java new file mode 100644 index 0000000..488f05a --- /dev/null +++ b/src/main/java/com/georgev22/api/scheduler/AsyncTask.java @@ -0,0 +1,112 @@ +package com.georgev22.api.scheduler; + +import com.georgev22.api.scheduler.interfaces.Worker; +import org.jetbrains.annotations.NotNull; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; + +class AsyncTask extends Task { + + private final LinkedList workers = new LinkedList<>(); + private final Map runners; + + AsyncTask(final Map runners, final Class clazz, final Object task, final int id, final long delay) { + super(clazz, task, id, delay); + this.runners = runners; + } + + @Override + public boolean isSync() { + return false; + } + + @Override + public void run() { + final Thread thread = Thread.currentThread(); + synchronized (workers) { + if (getPeriod() == Task.CANCEL) { + // Never continue running after cancelled. + // Checking this with the lock is important! + return; + } + workers.add( + new Worker() { + @Override + public @NotNull Thread getThread() { + return thread; + } + + @Override + public int getTaskId() { + return AsyncTask.this.getTaskId(); + } + + @Override + public @NotNull Class getOwner() { + return AsyncTask.this.getOwner(); + } + }); + } + Throwable thrown = null; + try { + super.run(); + } catch (final Throwable t) { + thrown = t; + throw new RuntimeException( + String.format( + "Class %s generated an exception while executing task %s", + getOwner().getSimpleName(), + getTaskId()), + t + ); + } finally { + // Cleanup is important for any async task, otherwise ghost tasks are everywhere + synchronized (workers) { + try { + final Iterator workers = this.workers.iterator(); + boolean removed = false; + while (workers.hasNext()) { + if (workers.next().getThread() == thread) { + workers.remove(); + removed = true; // Don't throw exception + break; + } + } + if (!removed) { + throw new IllegalStateException( + String.format( + "Unable to remove worker %s on task %s for %s", + thread.getName(), + getTaskId(), + getOwner().getSimpleName()), + thrown); // We don't want to lose the original exception, if any + } + } finally { + if (getPeriod() < 0 && workers.isEmpty()) { + // At this spot, we know we are the final async task being executed! + // Because we have the lock, nothing else is running or will run because delay < 0 + runners.remove(getTaskId()); + } + } + } + } + } + + LinkedList getWorkers() { + return workers; + } + + @Override + boolean cancel0() { + synchronized (workers) { + // Synchronizing here prevents race condition for a completing task + setPeriod(Task.CANCEL); + if (workers.isEmpty()) { + runners.remove(getTaskId()); + } + } + return true; + } +} diff --git a/src/main/java/com/georgev22/api/scheduler/Future.java b/src/main/java/com/georgev22/api/scheduler/Future.java new file mode 100644 index 0000000..ff771bb --- /dev/null +++ b/src/main/java/com/georgev22/api/scheduler/Future.java @@ -0,0 +1,103 @@ +package com.georgev22.api.scheduler; + + +import java.util.concurrent.*; + +class Future extends Task implements java.util.concurrent.Future { + + private final Callable callable; + private T value; + private Exception exception = null; + + Future(final Callable callable, final Class clazz, final int id) { + super(clazz, null, id, Task.NO_REPEATING); + this.callable = callable; + } + + @Override + public synchronized boolean cancel(final boolean mayInterruptIfRunning) { + if (getPeriod() != Task.NO_REPEATING) { + return false; + } + setPeriod(Task.CANCEL); + return true; + } + + @Override + public boolean isDone() { + final long period = this.getPeriod(); + return period != Task.NO_REPEATING && period != Task.PROCESS_FOR_FUTURE; + } + + @Override + public T get() throws CancellationException, InterruptedException, ExecutionException { + try { + return get(0, TimeUnit.MILLISECONDS); + } catch (final TimeoutException e) { + throw new Error(e); + } + } + + @Override + public synchronized T get(long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + timeout = unit.toMillis(timeout); + long period = this.getPeriod(); + long timestamp = timeout > 0 ? System.currentTimeMillis() : 0L; + while (true) { + if (period == Task.NO_REPEATING || period == Task.PROCESS_FOR_FUTURE) { + this.wait(timeout); + period = this.getPeriod(); + if (period == Task.NO_REPEATING || period == Task.PROCESS_FOR_FUTURE) { + if (timeout == 0L) { + continue; + } + timeout += timestamp - (timestamp = System.currentTimeMillis()); + if (timeout > 0) { + continue; + } + throw new TimeoutException(); + } + } + if (period == Task.CANCEL) { + throw new CancellationException(); + } + if (period == Task.DONE_FOR_FUTURE) { + if (exception == null) { + return value; + } + throw new ExecutionException(exception); + } + throw new IllegalStateException("Expected " + Task.NO_REPEATING + " to " + Task.DONE_FOR_FUTURE + ", got " + period); + } + } + + @Override + public void run() { + synchronized (this) { + if (getPeriod() == Task.CANCEL) { + return; + } + setPeriod(Task.PROCESS_FOR_FUTURE); + } + try { + value = callable.call(); + } catch (final Exception e) { + exception = e; + } finally { + synchronized (this) { + setPeriod(Task.DONE_FOR_FUTURE); + this.notifyAll(); + } + } + } + + @Override + synchronized boolean cancel0() { + if (getPeriod() != Task.NO_REPEATING) { + return false; + } + setPeriod(Task.CANCEL); + notifyAll(); + return true; + } +} diff --git a/src/main/java/com/georgev22/api/scheduler/Scheduler.java b/src/main/java/com/georgev22/api/scheduler/Scheduler.java new file mode 100644 index 0000000..494acee --- /dev/null +++ b/src/main/java/com/georgev22/api/scheduler/Scheduler.java @@ -0,0 +1,549 @@ +package com.georgev22.api.scheduler; + +import com.georgev22.api.scheduler.interfaces.Task; +import com.georgev22.api.scheduler.interfaces.Worker; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import org.apache.commons.lang.Validate; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.IntUnaryOperator; + +/** + * 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(com.georgev22.api.scheduler.Task, long)} or {@link #addTask(com.georgev22.api.scheduler.Task)}.
  • + *
  • 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 Scheduler implements com.georgev22.api.scheduler.interfaces.Scheduler { + + /** + * The start ID for the counter. + */ + private static final int START_ID = 1; + /** + * Increment the {@link #ids} field and reset it to the {@link #START_ID} if it reaches {@link Integer#MAX_VALUE} + */ + private static final IntUnaryOperator INCREMENT_IDS = previous -> { + // We reached the end, go back to the start! + if (previous == Integer.MAX_VALUE) { + return START_ID; + } + return previous + 1; + }; + /** + * Counter for IDs. Order doesn't matter, only uniqueness. + */ + private final AtomicInteger ids = new AtomicInteger(START_ID); + /** + * Current head of linked-list. This reference is always stale, {@link com.georgev22.api.scheduler.Task#next} is the live reference. + */ + private volatile com.georgev22.api.scheduler.Task head = new com.georgev22.api.scheduler.Task(); + /** + * Tail of a linked-list. AtomicReference only matters when adding to queue + */ + private final AtomicReference tail = new AtomicReference<>(head); + // If the tasks should run on the same tick they should be run FIFO + /** + * Main thread logic only + */ + private final PriorityQueue pending = new PriorityQueue<>(10, + Comparator.comparingLong(com.georgev22.api.scheduler.Task::getNextRun).thenComparingLong(com.georgev22.api.scheduler.Task::getCreatedAt)); + /** + * 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. + */ + private final ConcurrentHashMap runners = new ConcurrentHashMap<>(); + /** + * The sync task that is currently running on the main thread. + */ + private volatile com.georgev22.api.scheduler.Task currentTask = null; + private volatile int currentTick = -1; + private final Executor executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("Scheduler Thread - %d").build()); + private AsyncDebugger debugHead = new AsyncDebugger(-1, null, null) { + @Override + StringBuilder debugTo(StringBuilder string) { + return string; + } + }; + private AsyncDebugger debugTail = debugHead; + private static final int RECENT_TICKS; + + static { + RECENT_TICKS = 30; + } + + @Override + public int scheduleSyncDelayedTask(final @NotNull Class clazz, final @NotNull Runnable task) { + return this.scheduleSyncDelayedTask(clazz, task, 0L); + } + + @Override + public @NotNull Task runTask(@NotNull Class clazz, @NotNull Runnable runnable) { + return runTaskLater(clazz, runnable, 0L); + } + + @Override + public void runTask(@NotNull Class clazz, @NotNull Consumer task) throws IllegalArgumentException { + runTaskLater(clazz, task, 0L); + } + + @Deprecated + @Override + public int scheduleAsyncDelayedTask(final @NotNull Class clazz, final @NotNull Runnable task) { + return this.scheduleAsyncDelayedTask(clazz, task, 0L); + } + + @Override + public @NotNull Task runTaskAsynchronously(@NotNull Class clazz, @NotNull Runnable runnable) { + return runTaskLaterAsynchronously(clazz, runnable, 0L); + } + + @Override + public void runTaskAsynchronously(@NotNull Class clazz, @NotNull Consumer task) throws IllegalArgumentException { + runTaskLaterAsynchronously(clazz, task, 0L); + } + + @Override + public int scheduleSyncDelayedTask(final @NotNull Class clazz, final @NotNull Runnable task, final long delay) { + return this.scheduleSyncRepeatingTask(clazz, task, delay, com.georgev22.api.scheduler.Task.NO_REPEATING); + } + + @Override + public @NotNull Task runTaskLater(@NotNull Class clazz, @NotNull Runnable runnable, long delay) { + return runTaskTimer(clazz, runnable, delay, com.georgev22.api.scheduler.Task.NO_REPEATING); + } + + @Override + public void runTaskLater(@NotNull Class clazz, @NotNull Consumer task, long delay) throws IllegalArgumentException { + runTaskTimer(clazz, task, delay, com.georgev22.api.scheduler.Task.NO_REPEATING); + } + + @Deprecated + @Override + public int scheduleAsyncDelayedTask(final @NotNull Class clazz, final @NotNull Runnable task, final long delay) { + return this.scheduleAsyncRepeatingTask(clazz, task, delay, com.georgev22.api.scheduler.Task.NO_REPEATING); + } + + @Override + public @NotNull Task runTaskLaterAsynchronously(@NotNull Class clazz, @NotNull Runnable runnable, long delay) { + return runTaskTimerAsynchronously(clazz, runnable, delay, com.georgev22.api.scheduler.Task.NO_REPEATING); + } + + @Override + public void runTaskLaterAsynchronously(@NotNull Class clazz, @NotNull Consumer task, long delay) throws IllegalArgumentException { + runTaskTimerAsynchronously(clazz, task, delay, com.georgev22.api.scheduler.Task.NO_REPEATING); + } + + @Override + public void runTaskTimerAsynchronously(@NotNull Class clazz, @NotNull Consumer task, long delay, long period) throws IllegalArgumentException { + runTaskTimerAsynchronously(clazz, (Object) task, delay, com.georgev22.api.scheduler.Task.NO_REPEATING); + } + + @Override + public int scheduleSyncRepeatingTask(final @NotNull Class clazz, final @NotNull Runnable runnable, long delay, long period) { + return runTaskTimer(clazz, runnable, delay, period).getTaskId(); + } + + @Override + public @NotNull Task runTaskTimer(@NotNull Class clazz, @NotNull Runnable runnable, long delay, long period) { + return runTaskTimer(clazz, (Object) runnable, delay, period); + } + + @Override + public void runTaskTimer(@NotNull Class clazz, @NotNull Consumer task, long delay, long period) throws IllegalArgumentException { + runTaskTimer(clazz, (Object) task, delay, period); + } + + public Task runTaskTimer(Class clazz, Object runnable, long delay, long period) { + validate(clazz, runnable); + if (delay < 0L) { + delay = 0; + } + if (period == com.georgev22.api.scheduler.Task.ERROR) { + period = 1L; + } else if (period < com.georgev22.api.scheduler.Task.NO_REPEATING) { + period = com.georgev22.api.scheduler.Task.NO_REPEATING; + } + return handle(new com.georgev22.api.scheduler.Task(clazz, runnable, nextId(), period), delay); + } + + @Deprecated + @Override + public int scheduleAsyncRepeatingTask(final @NotNull Class clazz, final @NotNull Runnable runnable, long delay, long period) { + return runTaskTimerAsynchronously(clazz, runnable, delay, period).getTaskId(); + } + + @Override + public @NotNull Task runTaskTimerAsynchronously(@NotNull Class clazz, @NotNull Runnable runnable, long delay, long period) { + return runTaskTimerAsynchronously(clazz, (Object) runnable, delay, period); + } + + public Task runTaskTimerAsynchronously(Class clazz, Object runnable, long delay, long period) { + validate(clazz, runnable); + if (delay < 0L) { + delay = 0; + } + if (period == com.georgev22.api.scheduler.Task.ERROR) { + period = 1L; + } else if (period < com.georgev22.api.scheduler.Task.NO_REPEATING) { + period = com.georgev22.api.scheduler.Task.NO_REPEATING; + } + return handle(new AsyncTask(runners, clazz, runnable, nextId(), period), delay); + } + + @Override + public java.util.concurrent.@NotNull Future callSyncMethod(final @NotNull Class clazz, final @NotNull Callable task) { + validate(clazz, task); + final Future future = new Future<>(task, clazz, nextId()); + handle(future, 0L); + return future; + } + + @Override + public void cancelTask(final int taskId) { + if (taskId <= 0) { + return; + } + com.georgev22.api.scheduler.Task task = runners.get(taskId); + if (task != null) { + task.cancel0(); + } + task = new com.georgev22.api.scheduler.Task( + new Runnable() { + @Override + public void run() { + if (!check(Scheduler.this.temp)) { + check(Scheduler.this.pending); + } + } + + private boolean check(final Iterable collection) { + final Iterator tasks = collection.iterator(); + while (tasks.hasNext()) { + final com.georgev22.api.scheduler.Task task = tasks.next(); + if (task.getTaskId() == taskId) { + task.cancel0(); + tasks.remove(); + if (task.isSync()) { + runners.remove(taskId); + } + return true; + } + } + return false; + } + }); + handle(task, 0L); + for (com.georgev22.api.scheduler.Task taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) { + if (taskPending == task) { + return; + } + if (taskPending.getTaskId() == taskId) { + taskPending.cancel0(); + } + } + } + + @Override + public void cancelTasks(final @NotNull Class clazz) { + Validate.notNull(clazz, "Cannot cancel tasks of null class"); + final com.georgev22.api.scheduler.Task task = new com.georgev22.api.scheduler.Task( + new Runnable() { + @Override + public void run() { + check(Scheduler.this.pending); + check(Scheduler.this.temp); + } + + void check(final Iterable collection) { + final Iterator tasks = collection.iterator(); + while (tasks.hasNext()) { + final com.georgev22.api.scheduler.Task task = tasks.next(); + if (task.getOwner().equals(clazz)) { + task.cancel0(); + tasks.remove(); + if (task.isSync()) { + runners.remove(task.getTaskId()); + } + } + } + } + }); + handle(task, 0L); + for (com.georgev22.api.scheduler.Task taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) { + if (taskPending == task) { + break; + } + if (taskPending.getTaskId() != -1 && taskPending.getOwner().equals(clazz)) { + taskPending.cancel0(); + } + } + for (com.georgev22.api.scheduler.Task runner : runners.values()) { + if (runner.getOwner().equals(clazz)) { + runner.cancel0(); + } + } + } + + @Override + public boolean isCurrentlyRunning(final int taskId) { + final com.georgev22.api.scheduler.Task task = runners.get(taskId); + if (task == null) { + return false; + } + if (task.isSync()) { + return (task == currentTask); + } + final AsyncTask asyncTask = (AsyncTask) task; + synchronized (asyncTask.getWorkers()) { + return !asyncTask.getWorkers().isEmpty(); + } + } + + @Override + public boolean isQueued(final int taskId) { + if (taskId <= 0) { + return false; + } + for (com.georgev22.api.scheduler.Task task = head.getNext(); task != null; task = task.getNext()) { + if (task.getTaskId() == taskId) { + return task.getPeriod() >= com.georgev22.api.scheduler.Task.NO_REPEATING; // The task will run + } + } + com.georgev22.api.scheduler.Task task = runners.get(taskId); + return task != null && task.getPeriod() >= com.georgev22.api.scheduler.Task.NO_REPEATING; + } + + @Override + public @NotNull List getActiveWorkers() { + final ArrayList workers = new ArrayList<>(); + for (final com.georgev22.api.scheduler.Task 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 AsyncTask task = (AsyncTask) 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 @NotNull List getPendingTasks() { + final ArrayList truePending = new ArrayList<>(); + for (com.georgev22.api.scheduler.Task 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 (com.georgev22.api.scheduler.Task task : runners.values()) { + if (task.getPeriod() >= com.georgev22.api.scheduler.Task.NO_REPEATING) { + pending.add(task); + } + } + + for (final com.georgev22.api.scheduler.Task task : truePending) { + if (task.getPeriod() >= com.georgev22.api.scheduler.Task.NO_REPEATING && !pending.contains(task)) { + pending.add(task); + } + } + 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) { + this.currentTick = currentTick; + final List temp = this.temp; + parsePending(); + while (isReady(currentTick)) { + final com.georgev22.api.scheduler.Task task = pending.remove(); + if (task.getPeriod() < com.georgev22.api.scheduler.Task.NO_REPEATING) { + if (task.isSync()) { + runners.remove(task.getTaskId(), task); + } + parsePending(); + continue; + } + if (task.isSync()) { + currentTask = task; + try { + task.run(); + } catch (final Throwable throwable) { + throw new RuntimeException( + String.format( + "Task #%s for %s generated an exception", + task.getTaskId(), + task.getOwner().getSimpleName()), + throwable); + } finally { + currentTask = null; + } + parsePending(); + } else { + debugTail = debugTail.setNext(new AsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass())); + executor.execute(task); + // 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()); + } + } + pending.addAll(temp); + temp.clear(); + debugHead = debugHead.getNextHead(currentTick); + } + + private void addTask(final com.georgev22.api.scheduler.Task task) { + final AtomicReference tail = this.tail; + com.georgev22.api.scheduler.Task tailTask = tail.get(); + while (!tail.compareAndSet(tailTask, task)) { + tailTask = tail.get(); + } + tailTask.setNext(task); + } + + private com.georgev22.api.scheduler.Task handle(final com.georgev22.api.scheduler.Task task, final long delay) { + task.setNextRun(currentTick + delay); + addTask(task); + return task; + } + + private static void validate(final Class clazz, final Object task) { + Validate.notNull(clazz, "Class 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"); + } + + private int nextId() { + Validate.isTrue(runners.size() < Integer.MAX_VALUE, "There are already " + Integer.MAX_VALUE + " tasks scheduled! Cannot schedule more."); + int id; + do { + id = ids.updateAndGet(INCREMENT_IDS); + } while (runners.containsKey(id)); // Avoid generating duplicate IDs + return id; + } + + private void parsePending() { + com.georgev22.api.scheduler.Task head = this.head; + com.georgev22.api.scheduler.Task task = head.getNext(); + com.georgev22.api.scheduler.Task lastTask = head; + for (; task != null; task = (lastTask = task).getNext()) { + if (task.getTaskId() == -1) { + task.run(); + } else if (task.getPeriod() >= com.georgev22.api.scheduler.Task.NO_REPEATING) { + pending.add(task); + runners.put(task.getTaskId(), task); + } + } + // We split this because of the way things are ordered for all the async calls in Scheduler + // (it prevents race-conditions) + for (task = head; task != lastTask; task = head) { + head = task.getNext(); + task.setNext(null); + } + this.head = lastTask; + } + + private boolean isReady(final int currentTick) { + return !pending.isEmpty() && pending.peek().getNextRun() <= currentTick; + } + + @Override + public String toString() { + 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(); + } + + + @Deprecated + @Override + public int scheduleSyncDelayedTask(@NotNull Class clazz, @NotNull SchedulerRunnable task, long delay) { + throw new UnsupportedOperationException("Use SchedulerRunnable#runTaskLater(Class, long)"); + } + + @Deprecated + @Override + public int scheduleSyncDelayedTask(@NotNull Class clazz, @NotNull SchedulerRunnable task) { + throw new UnsupportedOperationException("Use SchedulerRunnable#runTask(Class)"); + } + + @Deprecated + @Override + public int scheduleSyncRepeatingTask(@NotNull Class clazz, @NotNull SchedulerRunnable task, long delay, long period) { + throw new UnsupportedOperationException("Use SchedulerRunnable#runTaskTimer(Class, long, long)"); + } + + @Deprecated + @Override + public @NotNull com.georgev22.api.scheduler.Task runTask(@NotNull Class clazz, @NotNull SchedulerRunnable task) throws IllegalArgumentException { + throw new UnsupportedOperationException("Use SchedulerRunnable#runTask(Class)"); + } + + @Deprecated + @Override + public @NotNull com.georgev22.api.scheduler.Task runTaskAsynchronously(@NotNull Class clazz, @NotNull SchedulerRunnable task) throws IllegalArgumentException { + throw new UnsupportedOperationException("Use SchedulerRunnable#runTaskAsynchronously(Class)"); + } + + @Deprecated + @Override + public @NotNull com.georgev22.api.scheduler.Task runTaskLater(@NotNull Class clazz, @NotNull SchedulerRunnable task, long delay) throws IllegalArgumentException { + throw new UnsupportedOperationException("Use SchedulerRunnable#runTaskLater(Class, long)"); + } + + @Deprecated + @Override + public @NotNull com.georgev22.api.scheduler.Task runTaskLaterAsynchronously(@NotNull Class clazz, @NotNull SchedulerRunnable task, long delay) throws IllegalArgumentException { + throw new UnsupportedOperationException("Use SchedulerRunnable#runTaskLaterAsynchronously(Class, long)"); + } + + @Deprecated + @Override + public @NotNull com.georgev22.api.scheduler.Task runTaskTimer(@NotNull Class clazz, @NotNull SchedulerRunnable task, long delay, long period) throws IllegalArgumentException { + throw new UnsupportedOperationException("Use SchedulerRunnable#runTaskTimer(Class, long, long)"); + } + + @Deprecated + @Override + public @NotNull com.georgev22.api.scheduler.Task runTaskTimerAsynchronously(@NotNull Class clazz, @NotNull SchedulerRunnable task, long delay, long period) throws IllegalArgumentException { + throw new UnsupportedOperationException("Use SchedulerRunnable#runTaskTimerAsynchronously(Class, long, long)"); + } +} diff --git a/src/main/java/com/georgev22/api/scheduler/SchedulerManager.java b/src/main/java/com/georgev22/api/scheduler/SchedulerManager.java new file mode 100644 index 0000000..1bbbd7b --- /dev/null +++ b/src/main/java/com/georgev22/api/scheduler/SchedulerManager.java @@ -0,0 +1,10 @@ +package com.georgev22.api.scheduler; + +public class SchedulerManager { + + private final static Scheduler scheduler = new Scheduler(); + + public static Scheduler getScheduler() { + return scheduler; + } +} diff --git a/src/main/java/com/georgev22/api/scheduler/SchedulerRunnable.java b/src/main/java/com/georgev22/api/scheduler/SchedulerRunnable.java new file mode 100644 index 0000000..b547444 --- /dev/null +++ b/src/main/java/com/georgev22/api/scheduler/SchedulerRunnable.java @@ -0,0 +1,170 @@ +package com.georgev22.api.scheduler; + +import com.georgev22.api.scheduler.interfaces.Scheduler; +import com.georgev22.api.scheduler.interfaces.Task; +import org.jetbrains.annotations.NotNull; + +public abstract class SchedulerRunnable implements Runnable { + + private Task task; + + /** + * Returns true if this task has been cancelled. + * + * @return true if the task has been cancelled + * @throws IllegalStateException if task was not scheduled yet + */ + public synchronized boolean isCancelled() throws IllegalStateException { + checkScheduled(); + return task.isCancelled(); + } + + /** + * Attempts to cancel this task. + * + * @throws IllegalStateException if task was not scheduled yet + */ + public synchronized void cancel() throws IllegalStateException { + SchedulerManager.getScheduler().cancelTask(getTaskId()); + } + + /** + * Schedules this in the Bukkit scheduler to run on next tick. + * + * @param clazz the reference to the clazz, scheduling task + * @return a Task that contains the id number + * @throws IllegalArgumentException if clazz, is null + * @throws IllegalStateException if this was already scheduled + * @see Scheduler#runTask(Class, Runnable) + */ + @NotNull + public synchronized Task runTask(@NotNull Class clazz) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(SchedulerManager.getScheduler().runTask(clazz, (Runnable) this)); + } + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Schedules this in the Bukkit scheduler to run asynchronously. + * + * @param clazz the reference to the clazz, scheduling task + * @return a Task that contains the id number + * @throws IllegalArgumentException if clazz, is null + * @throws IllegalStateException if this was already scheduled + * @see Scheduler#runTaskAsynchronously(Class, Runnable) + */ + @NotNull + public synchronized Task runTaskAsynchronously(@NotNull Class clazz) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(SchedulerManager.getScheduler().runTaskAsynchronously(clazz, (Runnable) this)); + } + + /** + * Schedules this to run after the specified number of server ticks. + * + * @param clazz the reference to the clazz, scheduling task + * @param delay the ticks to wait before running the task + * @return a Task that contains the id number + * @throws IllegalArgumentException if clazz, is null + * @throws IllegalStateException if this was already scheduled + * @see Scheduler#runTaskLater(Class, Runnable, long) + */ + @NotNull + public synchronized Task runTaskLater(@NotNull Class clazz, long delay) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(SchedulerManager.getScheduler().runTaskLater(clazz, (Runnable) this, delay)); + } + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Schedules this to run asynchronously after the specified number of + * server ticks. + * + * @param clazz the reference to the clazz, scheduling task + * @param delay the ticks to wait before running the task + * @return a Task that contains the id number + * @throws IllegalArgumentException if clazz, is null + * @throws IllegalStateException if this was already scheduled + * @see Scheduler#runTaskLaterAsynchronously(Class, Runnable, long) + */ + @NotNull + public synchronized Task runTaskLaterAsynchronously(@NotNull Class clazz, long delay) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(SchedulerManager.getScheduler().runTaskLaterAsynchronously(clazz, (Runnable) this, delay)); + } + + /** + * Schedules this to repeatedly run until cancelled, starting after the + * specified number of server ticks. + * + * @param clazz the reference to the clazz, scheduling task + * @param delay the ticks to wait before running the task + * @param period the ticks to wait between runs + * @return a Task that contains the id number + * @throws IllegalArgumentException if clazz, is null + * @throws IllegalStateException if this was already scheduled + * @see Scheduler#runTaskTimer(Class, Runnable, long, long) + */ + @NotNull + public synchronized Task runTaskTimer(@NotNull Class clazz, long delay, long period) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(SchedulerManager.getScheduler().runTaskTimer(clazz, (Runnable) this, delay, period)); + } + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Schedules this to repeatedly run asynchronously until cancelled, + * starting after the specified number of server ticks. + * + * @param clazz the reference to the clazz, scheduling task + * @param delay the ticks to wait before running the task for the first + * time + * @param period the ticks to wait between runs + * @return a Task that contains the id number + * @throws IllegalArgumentException if clazz, is null + * @throws IllegalStateException if this was already scheduled + * @see Scheduler#runTaskTimerAsynchronously(Class, Runnable, long, + * long) + */ + @NotNull + public synchronized Task runTaskTimerAsynchronously(@NotNull Class clazz, long delay, long period) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(SchedulerManager.getScheduler().runTaskTimerAsynchronously(clazz, (Runnable) this, delay, period)); + } + + /** + * Gets the task id for this runnable. + * + * @return the task id that this runnable was scheduled as + * @throws IllegalStateException if task was not scheduled yet + */ + public synchronized int getTaskId() throws IllegalStateException { + checkScheduled(); + return task.getTaskId(); + } + + private void checkScheduled() { + if (task == null) { + throw new IllegalStateException("Not scheduled yet"); + } + } + + private void checkNotYetScheduled() { + if (task != null) { + throw new IllegalStateException("Already scheduled as " + task.getTaskId()); + } + } + + @NotNull + private Task setupTask(@NotNull final Task task) { + this.task = task; + return task; + } + +} diff --git a/src/main/java/com/georgev22/api/scheduler/Task.java b/src/main/java/com/georgev22/api/scheduler/Task.java new file mode 100644 index 0000000..e91be7b --- /dev/null +++ b/src/main/java/com/georgev22/api/scheduler/Task.java @@ -0,0 +1,133 @@ +package com.georgev22.api.scheduler; + +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; + +class Task implements com.georgev22.api.scheduler.interfaces.Task, Runnable { + + private volatile Task next = null; + public static final int ERROR = 0; + public static final int NO_REPEATING = -1; + public static final int CANCEL = -2; + public static final int PROCESS_FOR_FUTURE = -3; + public static final int DONE_FOR_FUTURE = -4; + /** + * -1 means no repeating
    + * -2 means cancel
    + * -3 means processing for Future
    + * -4 means done for Future
    + * Never 0
    + * >0 means number of ticks to wait between each execution + */ + private volatile long period; + private long nextRun; + private final Runnable rTask; + private final Consumer cTask; + private final Class clazz; + private final int id; + private final long createdAt = System.nanoTime(); + + Task() { + this(null, null, com.georgev22.api.scheduler.Task.NO_REPEATING, com.georgev22.api.scheduler.Task.NO_REPEATING); + } + + Task(final Object task) { + this(null, task, com.georgev22.api.scheduler.Task.NO_REPEATING, com.georgev22.api.scheduler.Task.NO_REPEATING); + } + + Task(final Class clazz, final Object task, final int id, final long period) { + this.clazz = clazz; + if (task instanceof Runnable) { + this.rTask = (Runnable) task; + this.cTask = null; + } else if (task instanceof Consumer) { + this.cTask = (Consumer) task; + this.rTask = null; + } else if (task == null) { + // Head or Future task + this.rTask = null; + this.cTask = null; + } else { + throw new AssertionError("Illegal task class " + task); + } + this.id = id; + this.period = period; + } + + @Override + public final int getTaskId() { + return id; + } + + @Override + public final @NotNull Class getOwner() { + return clazz; + } + + @Override + public boolean isSync() { + return true; + } + + @Override + public void run() { + if (rTask != null) { + rTask.run(); + } else { + cTask.accept(this); + } + } + + long getCreatedAt() { + return createdAt; + } + + long getPeriod() { + return period; + } + + void setPeriod(long period) { + this.period = period; + } + + long getNextRun() { + return nextRun; + } + + void setNextRun(long nextRun) { + this.nextRun = nextRun; + } + + Task getNext() { + return next; + } + + void setNext(Task next) { + this.next = next; + } + + Class getTaskClass() { + return (rTask != null) ? rTask.getClass() : ((cTask != null) ? cTask.getClass() : null); + } + + @Override + public boolean isCancelled() { + return (period == com.georgev22.api.scheduler.Task.CANCEL); + } + + @Override + public void cancel() { + SchedulerManager.getScheduler().cancelTask(id); + } + + /** + * This method properly sets the status to cancelled, synchronizing when required. + * + * @return false if it is a future task that has already begun execution, true otherwise + */ + boolean cancel0() { + setPeriod(com.georgev22.api.scheduler.Task.CANCEL); + return true; + } +} diff --git a/src/main/java/com/georgev22/api/scheduler/interfaces/Scheduler.java b/src/main/java/com/georgev22/api/scheduler/interfaces/Scheduler.java new file mode 100644 index 0000000..6adac84 --- /dev/null +++ b/src/main/java/com/georgev22/api/scheduler/interfaces/Scheduler.java @@ -0,0 +1,448 @@ +package com.georgev22.api.scheduler.interfaces; + + +import com.georgev22.api.scheduler.SchedulerRunnable; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.function.Consumer; + +public interface Scheduler { + + /** + * Schedules a once off task to occur after a delay. + *

    + * This task will be executed by the main server thread. + * + * @param clazz clazz that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing task + * @return Task id number (-1 if scheduling failed) + */ + int scheduleSyncDelayedTask(@NotNull Class clazz, @NotNull Runnable task, long delay); + + /** + * @param clazz clazz that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing task + * @return Task id number (-1 if scheduling failed) + * @deprecated Use {@link SchedulerRunnable#runTaskLater(Class, long)} + */ + @Deprecated + int scheduleSyncDelayedTask(@NotNull Class clazz, @NotNull SchedulerRunnable task, long delay); + + /** + * Schedules a once off task to occur as soon as possible. + *

    + * This task will be executed by the main server thread. + * + * @param clazz clazz that owns the task + * @param task Task to be executed + * @return Task id number (-1 if scheduling failed) + */ + int scheduleSyncDelayedTask(@NotNull Class clazz, @NotNull Runnable task); + + /** + * @param clazz clazz that owns the task + * @param task Task to be executed + * @return Task id number (-1 if scheduling failed) + * @deprecated Use {@link SchedulerRunnable#runTask(Class)} + */ + @Deprecated + int scheduleSyncDelayedTask(@NotNull Class clazz, @NotNull SchedulerRunnable task); + + /** + * Schedules a repeating task. + *

    + * This task will be executed by the main server thread. + * + * @param clazz clazz that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing first repeat + * @param period Period in server ticks of the task + * @return Task id number (-1 if scheduling failed) + */ + int scheduleSyncRepeatingTask(@NotNull Class clazz, @NotNull Runnable task, long delay, long period); + + /** + * @param clazz clazz that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing first repeat + * @param period Period in server ticks of the task + * @return Task id number (-1 if scheduling failed) + * @deprecated Use {@link SchedulerRunnable#runTaskTimer(Class, long, long)} + */ + @Deprecated + int scheduleSyncRepeatingTask(@NotNull Class clazz, @NotNull SchedulerRunnable task, long delay, long period); + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Schedules a once off task to occur after a delay. This task will be + * executed by a thread managed by the scheduler. + * + * @param clazz clazz that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing task + * @return Task id number (-1 if scheduling failed) + * @deprecated This name is misleading, as it does not schedule "a sync" + * task, but rather, "an async" task + */ + @Deprecated + int scheduleAsyncDelayedTask(@NotNull Class clazz, @NotNull Runnable task, long delay); + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Schedules a once off task to occur as soon as possible. This task will + * be executed by a thread managed by the scheduler. + * + * @param clazz clazz that owns the task + * @param task Task to be executed + * @return Task id number (-1 if scheduling failed) + * @deprecated This name is misleading, as it does not schedule "a sync" + * task, but rather, "an async" task + */ + @Deprecated + int scheduleAsyncDelayedTask(@NotNull Class clazz, @NotNull Runnable task); + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Schedules a repeating task. This task will be executed by a thread + * managed by the scheduler. + * + * @param clazz clazz that owns the task + * @param task Task to be executed + * @param delay Delay in server ticks before executing first repeat + * @param period Period in server ticks of the task + * @return Task id number (-1 if scheduling failed) + * @deprecated This name is misleading, as it does not schedule "a sync" + * task, but rather, "an async" task + */ + @Deprecated + int scheduleAsyncRepeatingTask(@NotNull Class clazz, @NotNull Runnable task, long delay, long period); + + /** + * Calls a method on the main thread and returns a Future object. This + * task will be executed by the main server thread. + *

      + *
    • Note: The Future.get() methods must NOT be called from the main + * thread. + *
    • Note2: There is at least an average of 10ms latency until the + * isDone() method returns true. + *
    + * + * @param The callable's return type + * @param clazz clazz that owns the task + * @param task Task to be executed + * @return Future object related to the task + */ + @NotNull Future callSyncMethod(@NotNull Class clazz, @NotNull Callable task); + + /** + * Removes task from scheduler. + * + * @param taskId Id number of task to be removed + */ + void cancelTask(int taskId); + + /** + * Removes all tasks associated with a particular class from the + * scheduler. + * + * @param clazz Owner of tasks to be removed + */ + void cancelTasks(@NotNull Class clazz); + + /** + * Check if the task currently running. + *

    + * A repeating task might not be running currently, but will be running in + * the future. A task that has finished, and does not repeat, will not be + * running ever again. + *

    + * Explicitly, a task is running if there exists a thread for it, and that + * thread is alive. + * + * @param taskId The task to check. + *

    + * @return If the task is currently running. + */ + boolean isCurrentlyRunning(int taskId); + + /** + * Check if the task queued to be run later. + *

    + * If a repeating task is currently running, it might not be queued now + * but could be in the future. A task that is not queued, and not running, + * will not be queued again. + * + * @param taskId The task to check. + *

    + * @return If the task is queued to be run. + */ + boolean isQueued(int taskId); + + /** + * Returns a list of all active workers. + *

    + * This list contains asynch tasks that are being executed by separate + * threads. + * + * @return Active workers + */ + @NotNull List getActiveWorkers(); + + /** + * Returns a list of all pending tasks. The ordering of the tasks is not + * related to their order of execution. + * + * @return Active workers + */ + @NotNull List getPendingTasks(); + + /** + * Returns a task that will run on the next server tick. + * + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @return a Task that contains the id number + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + */ + @NotNull Task runTask(@NotNull Class clazz, @NotNull Runnable task) throws IllegalArgumentException; + + /** + * Returns a task that will run on the next server tick. + * + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + */ + void runTask(@NotNull Class clazz, @NotNull Consumer task) throws IllegalArgumentException; + + /** + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @return a Task that contains the id number + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + * @deprecated Use {@link SchedulerRunnable#runTask(Class)} + */ + @Deprecated + @NotNull Task runTask(@NotNull Class clazz, @NotNull SchedulerRunnable task) throws IllegalArgumentException; + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Returns a task that will run asynchronously. + * + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @return a Task that contains the id number + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + */ + @NotNull Task runTaskAsynchronously(@NotNull Class clazz, @NotNull Runnable task) throws IllegalArgumentException; + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Returns a task that will run asynchronously. + * + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + */ + void runTaskAsynchronously(@NotNull Class clazz, @NotNull Consumer task) throws IllegalArgumentException; + + /** + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @return a Task that contains the id number + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + * @deprecated Use {@link SchedulerRunnable#runTaskAsynchronously(Class)} + */ + @Deprecated + @NotNull Task runTaskAsynchronously(@NotNull Class clazz, @NotNull SchedulerRunnable task) throws IllegalArgumentException; + + /** + * Returns a task that will run after the specified number of server + * ticks. + * + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @return a Task that contains the id number + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + */ + @NotNull Task runTaskLater(@NotNull Class clazz, @NotNull Runnable task, long delay) throws IllegalArgumentException; + + /** + * Returns a task that will run after the specified number of server + * ticks. + * + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + */ + void runTaskLater(@NotNull Class clazz, @NotNull Consumer task, long delay) throws IllegalArgumentException; + + /** + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @return a Task that contains the id number + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + * @deprecated Use {@link SchedulerRunnable#runTaskLater(Class, long)} + */ + @Deprecated + @NotNull Task runTaskLater(@NotNull Class clazz, @NotNull SchedulerRunnable task, long delay) throws IllegalArgumentException; + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Returns a task that will run asynchronously after the specified number + * of server ticks. + * + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @return a Task that contains the id number + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + */ + @NotNull Task runTaskLaterAsynchronously(@NotNull Class clazz, @NotNull Runnable task, long delay) throws IllegalArgumentException; + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Returns a task that will run asynchronously after the specified number + * of server ticks. + * + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + */ + void runTaskLaterAsynchronously(@NotNull Class clazz, @NotNull Consumer task, long delay) throws IllegalArgumentException; + + /** + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @return a Task that contains the id number + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + * @deprecated Use {@link SchedulerRunnable#runTaskLaterAsynchronously(Class, long)} + */ + @Deprecated + @NotNull Task runTaskLaterAsynchronously(@NotNull Class clazz, @NotNull SchedulerRunnable task, long delay) throws IllegalArgumentException; + + /** + * Returns a task that will repeatedly run until cancelled, starting after + * the specified number of server ticks. + * + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @param period the ticks to wait between runs + * @return a Task that contains the id number + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + */ + @NotNull Task runTaskTimer(@NotNull Class clazz, @NotNull Runnable task, long delay, long period) throws IllegalArgumentException; + + /** + * Returns a task that will repeatedly run until cancelled, starting after + * the specified number of server ticks. + * + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @param period the ticks to wait between runs + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + */ + void runTaskTimer(@NotNull Class clazz, @NotNull Consumer task, long delay, long period) throws IllegalArgumentException; + + /** + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task + * @param period the ticks to wait between runs + * @return a Task that contains the id number + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + * @deprecated Use {@link SchedulerRunnable#runTaskTimer(Class, long, long)} + */ + @Deprecated + @NotNull Task runTaskTimer(@NotNull Class clazz, @NotNull SchedulerRunnable task, long delay, long period) throws IllegalArgumentException; + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Returns a task that will repeatedly run asynchronously until cancelled, + * starting after the specified number of server ticks. + * + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task for the first + * time + * @param period the ticks to wait between runs + * @return a Task that contains the id number + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + */ + @NotNull Task runTaskTimerAsynchronously(@NotNull Class clazz, @NotNull Runnable task, long delay, long period) throws IllegalArgumentException; + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

    + * Returns a task that will repeatedly run asynchronously until cancelled, + * starting after the specified number of server ticks. + * + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task for the first + * time + * @param period the ticks to wait between runs + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + */ + void runTaskTimerAsynchronously(@NotNull Class clazz, @NotNull Consumer task, long delay, long period) throws IllegalArgumentException; + + /** + * @param clazz the reference to the class scheduling task + * @param task the task to be run + * @param delay the ticks to wait before running the task for the first + * time + * @param period the ticks to wait between runs + * @return a Task that contains the id number + * @throws IllegalArgumentException if class is null + * @throws IllegalArgumentException if task is null + * @deprecated Use {@link SchedulerRunnable#runTaskTimerAsynchronously(Class, long, long)} + */ + @Deprecated + @NotNull Task runTaskTimerAsynchronously(@NotNull Class clazz, @NotNull SchedulerRunnable task, long delay, long period) throws IllegalArgumentException; +} diff --git a/src/main/java/com/georgev22/api/scheduler/interfaces/Task.java b/src/main/java/com/georgev22/api/scheduler/interfaces/Task.java new file mode 100644 index 0000000..7ff6213 --- /dev/null +++ b/src/main/java/com/georgev22/api/scheduler/interfaces/Task.java @@ -0,0 +1,42 @@ +package com.georgev22.api.scheduler.interfaces; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents a task being executed by the scheduler + */ +public interface Task { + + /** + * Returns the taskId for the task. + * + * @return Task id number + */ + int getTaskId(); + + /** + * Returns the Class that owns this task. + * + * @return The Class that owns the task + */ + @NotNull Class getOwner(); + + /** + * Returns true if the Task is a sync task. + * + * @return true if the task is run by main thread + */ + boolean isSync(); + + /** + * Returns true if this task has been cancelled. + * + * @return true if the task has been cancelled + */ + boolean isCancelled(); + + /** + * Will attempt to cancel this task. + */ + void cancel(); +} diff --git a/src/main/java/com/georgev22/api/scheduler/interfaces/Worker.java b/src/main/java/com/georgev22/api/scheduler/interfaces/Worker.java new file mode 100644 index 0000000..9d700d0 --- /dev/null +++ b/src/main/java/com/georgev22/api/scheduler/interfaces/Worker.java @@ -0,0 +1,34 @@ +package com.georgev22.api.scheduler.interfaces; + +import org.jetbrains.annotations.NotNull; + +/** + * Represents a worker thread for the scheduler. This gives information about + * the Thread object for the task, owner of the task and the taskId. + *

    + * Workers are used to execute async tasks. + */ +public interface Worker { + + /** + * Returns the taskId for the task being executed by this worker. + * + * @return Task id number + */ + int getTaskId(); + + /** + * Returns the Class that owns this task. + * + * @return The Class that owns the task + */ + @NotNull Class getOwner(); + + /** + * Returns the thread for the worker. + * + * @return The Thread object for the worker + */ + @NotNull Thread getThread(); + +}