Skip to content

Commit

Permalink
feat(Scheduler): Bukkit Scheduler for java applications
Browse files Browse the repository at this point in the history
  • Loading branch information
GeorgeV220 committed Nov 3, 2022
1 parent 787c304 commit 284be61
Show file tree
Hide file tree
Showing 10 changed files with 1,636 additions and 0 deletions.
35 changes: 35 additions & 0 deletions src/main/java/com/georgev22/api/scheduler/AsyncDebugger.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
112 changes: 112 additions & 0 deletions src/main/java/com/georgev22/api/scheduler/AsyncTask.java
Original file line number Diff line number Diff line change
@@ -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<Worker> workers = new LinkedList<>();
private final Map<Integer, Task> runners;

AsyncTask(final Map<Integer, Task> 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<Worker> 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<Worker> 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;
}
}
103 changes: 103 additions & 0 deletions src/main/java/com/georgev22/api/scheduler/Future.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.georgev22.api.scheduler;


import java.util.concurrent.*;

class Future<T> extends Task implements java.util.concurrent.Future<T> {

private final Callable<T> callable;
private T value;
private Exception exception = null;

Future(final Callable<T> 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;
}
}
Loading

0 comments on commit 284be61

Please sign in to comment.