-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Scheduler): Bukkit Scheduler for java applications
- Loading branch information
1 parent
787c304
commit 284be61
Showing
10 changed files
with
1,636 additions
and
0 deletions.
There are no files selected for viewing
35 changes: 35 additions & 0 deletions
35
src/main/java/com/georgev22/api/scheduler/AsyncDebugger.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
112
src/main/java/com/georgev22/api/scheduler/AsyncTask.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.