mirror of
https://github.com/garbagemule/MobArena.git
synced 2025-01-26 18:21:19 +01:00
Add Timer framework.
This framework consists of interfaces and classes for managing various kinds of timers. The Timer interface models a timer with configurable tick intervals. The TimerCallback interface provides a means of adding logic to the following timer events: onStart, onStop, onFinish and onTick. Timer implementations include a CountdownTimer (kitchen timer), and a StopwatchTimer, which should both be self-explanatory. The purpose of this framework is to add a means of creating a very generalized approach to timers, in hopes of abstracting away some of the Bukkit scheduling for easy maintenance and increased portability. Future work: - Port auto-start-timer - Port boss abilities - Port spawner - Create 'delay-timer' as per DBO ticket request. - Create various types of cooldowns, delays, etc.
This commit is contained in:
parent
f85f9e20d7
commit
a95ab27d5e
40
src/com/garbagemule/MobArena/util/timer/AbstractTimer.java
Normal file
40
src/com/garbagemule/MobArena/util/timer/AbstractTimer.java
Normal file
@ -0,0 +1,40 @@
|
||||
package com.garbagemule.MobArena.util.timer;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
public abstract class AbstractTimer implements Timer {
|
||||
protected Plugin plugin;
|
||||
protected TimerCallback callback;
|
||||
protected long interval;
|
||||
|
||||
public AbstractTimer(Plugin plugin, long interval, TimerCallback callback) {
|
||||
this.plugin = plugin;
|
||||
this.callback = callback;
|
||||
|
||||
setInterval(interval);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCallback(TimerCallback callback) {
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("Callback may not be null.");
|
||||
}
|
||||
if (this.callback != null) {
|
||||
throw new IllegalStateException("Timer already has a callback.");
|
||||
}
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInterval(long interval) {
|
||||
if (interval <= 0l) {
|
||||
throw new IllegalArgumentException("Tick interval must be positive.");
|
||||
}
|
||||
this.interval = interval;
|
||||
}
|
||||
}
|
211
src/com/garbagemule/MobArena/util/timer/CountdownTimer.java
Normal file
211
src/com/garbagemule/MobArena/util/timer/CountdownTimer.java
Normal file
@ -0,0 +1,211 @@
|
||||
package com.garbagemule.MobArena.util.timer;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
/**
|
||||
* A simple implementation of a generic countdown timer, which has an initial
|
||||
* duration and a tick interval.
|
||||
* <p>
|
||||
* Every time the tick interval has passed, the {@code onTick()} method on the
|
||||
* underlying {@link TimerCallback} is called. If the tick interval is equal
|
||||
* to the timer duration, {@code onTick()} is never called.
|
||||
* <p>
|
||||
* When the timer "runs out" (when the duration has passed), the timer calls
|
||||
* the {@code onFinish()} method on the callback. In case that the duration
|
||||
* is not divisible by the tick interval, the final interval will be shorter
|
||||
* than the previous intervals to make sure the timer ends no later than it
|
||||
* should.
|
||||
* <p>
|
||||
* Stopping the timer prematurely via the {@link #stop()} method causes the
|
||||
* timer to immediately call the {@code onStop()} method on the callback,
|
||||
* and any subsequent ticks will be ignored. The timer supports stopping and
|
||||
* (re)starting in the same tick.
|
||||
*/
|
||||
public class CountdownTimer extends AbstractTimer {
|
||||
private long duration;
|
||||
private long remaining;
|
||||
|
||||
private Timer timer;
|
||||
|
||||
/**
|
||||
* Create a CountdownTimer that will call the {@code onTick()} method on
|
||||
* the callback every {@code interval} ticks. Furthermore, the timer will
|
||||
* either call the {@code onFinish()} method on the callback when it ends,
|
||||
* or the {@code onStop()} method if the timer is stopped prematurely.
|
||||
*
|
||||
* @param plugin the plugin responsible for the timer
|
||||
* @param duration the duration of the timer; must be non-negative
|
||||
* @param interval the amount of ticks between each {@code onTick()} call
|
||||
* on the callback object; must be positive and less than
|
||||
* or equal to {@code duration}
|
||||
* @param callback a callback object
|
||||
*/
|
||||
public CountdownTimer(Plugin plugin, long duration, long interval, TimerCallback callback) {
|
||||
super(plugin, interval, callback);
|
||||
|
||||
if (duration < 0l) {
|
||||
throw new IllegalArgumentException("Duration must be non-negative.");
|
||||
}
|
||||
this.duration = duration;
|
||||
this.remaining = 0l;
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CountdownTimer with the given duration and tick interval.
|
||||
* <p>
|
||||
* This constructor leaves the timer in an inconsistent state until the
|
||||
* {@link #setCallback(TimerCallback)} method is called with a valid
|
||||
* callback object.
|
||||
*
|
||||
* @param plugin the plugin responsible for the timer
|
||||
* @param duration the duration of the timer; must be non-negative
|
||||
* @param interval the amount of ticks between each {@code onTick()} call
|
||||
* on the callback object; must be positive and less than
|
||||
* or equal to {@code duration}
|
||||
*/
|
||||
public CountdownTimer(Plugin plugin, long duration, long interval) {
|
||||
this(plugin, duration, interval, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CountdownTimer that will never tick. The timer will either
|
||||
* call the {@code onFinish()} method on the callback when it ends, or
|
||||
* the {@code onStop()} method if the timer is stopped prematurely.
|
||||
*
|
||||
* @param plugin the plugin responsible for the timer
|
||||
* @param duration the duration of the timer; must be non-negative
|
||||
* @param callback a callback object
|
||||
*/
|
||||
public CountdownTimer(Plugin plugin, long duration, TimerCallback callback) {
|
||||
this(plugin, duration, duration, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CountdownTimer that will never tick. The timer will either
|
||||
* call the {@code onFinish()} method on the callback when it ends, or
|
||||
* the {@code onStop()} method if the timer is stopped prematurely.
|
||||
* <p>
|
||||
* This constructor leaves the timer in an inconsistent state until the
|
||||
* {@link #setCallback(TimerCallback)} method is called with a valid
|
||||
* callback object.
|
||||
*
|
||||
* @param plugin the plugin responsible for the timer
|
||||
* @param duration the duration of the timer; must be non-negative
|
||||
*/
|
||||
public CountdownTimer(Plugin plugin, long duration) {
|
||||
this(plugin, duration, duration, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the timer.
|
||||
* <p>
|
||||
* The timer will start counting down from the duration specified in the
|
||||
* constructor, and count down {@code interval} ticks every time it ticks,
|
||||
* as well as call the {@code onTick()} method on the callback.
|
||||
* <p>
|
||||
* If no interval was provided in the constructor, the timer will never
|
||||
* call the {@code onTick()} method, but only the {@code onFinish()} when
|
||||
* the timer runs out, or the {@code onStop()} method, if the timer is
|
||||
* stopped prematurely via the {@link #stop()} method.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void start() {
|
||||
if (timer != null) {
|
||||
return;
|
||||
}
|
||||
remaining = duration;
|
||||
callback.onStart();
|
||||
timer = new Timer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the timer prematurely.
|
||||
* <p>
|
||||
* This will call the {@code onStop()} method on the callback, and reset
|
||||
* the timer to a state in which calling the {@link #start()} method will
|
||||
* restart the timer.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void stop() {
|
||||
if (timer == null) {
|
||||
return;
|
||||
}
|
||||
timer.stop();
|
||||
timer = null;
|
||||
remaining = 0l;
|
||||
callback.onStop();
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public synchronized boolean isRunning() {
|
||||
return timer != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the duration of the timer.
|
||||
*
|
||||
* @return the duration of the timer in server ticks
|
||||
*/
|
||||
public synchronized long getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remaining number of ticks before this timer runs out.
|
||||
*
|
||||
* @return the remaining number of server ticks
|
||||
*/
|
||||
public synchronized long getRemaining() {
|
||||
return remaining;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal timer class for the actual legwork. The timer will reschedule
|
||||
* itself after every tick, if rescheduling is applicable. Furthermore,
|
||||
* the timer will auto-start on creation to avoid having to schedule it
|
||||
* from the {@link #start()} method.
|
||||
*/
|
||||
private class Timer implements Runnable {
|
||||
private BukkitTask task;
|
||||
|
||||
public Timer() {
|
||||
reschedule();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (CountdownTimer.this) {
|
||||
remaining -= interval;
|
||||
|
||||
// If we're done, call onFinish() and bail
|
||||
if (remaining <= 0l) {
|
||||
callback.onFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, tick
|
||||
callback.onTick();
|
||||
|
||||
// If stop() was called from onTick(), don't reschedule
|
||||
if (task != null) {
|
||||
reschedule();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void stop() {
|
||||
task.cancel();
|
||||
task = null;
|
||||
}
|
||||
|
||||
private synchronized void reschedule() {
|
||||
// Make sure the timer stops on time
|
||||
long nextInterval = (remaining < interval) ? remaining : interval;
|
||||
task = Bukkit.getScheduler().runTaskLater(plugin, this, nextInterval);
|
||||
}
|
||||
}
|
||||
}
|
129
src/com/garbagemule/MobArena/util/timer/StopwatchTimer.java
Normal file
129
src/com/garbagemule/MobArena/util/timer/StopwatchTimer.java
Normal file
@ -0,0 +1,129 @@
|
||||
package com.garbagemule.MobArena.util.timer;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
/**
|
||||
* A simple implementation of a generic stopwatch timer, which periodically
|
||||
* ticks according to a given tick interval.
|
||||
* <p>
|
||||
* Every time the tick interval has passed, the {@code onTick()} method on the
|
||||
* underlying {@link TimerCallback} is called.
|
||||
* <p>
|
||||
* This timer never runs out, and it must be manually stopped via the
|
||||
* {@link #stop()} method, which causes the timer to immediately call the
|
||||
* {@code onStop()} method on the callback, and any subsequent ticks will be
|
||||
* ignored. The timer supports stopping and (re)starting in the same tick.
|
||||
*/
|
||||
public class StopwatchTimer extends AbstractTimer {
|
||||
private Timer timer;
|
||||
|
||||
/**
|
||||
* Create a StopwatchTimer that will call the {@code onTick()} method on
|
||||
* the callback every {@code interval} ticks. Furthermore, the timer will
|
||||
* either call the {@code onFinish()} method on the callback when it ends,
|
||||
* or the {@code onStop()} method if the timer is stopped prematurely.
|
||||
*
|
||||
* @param plugin the plugin responsible for the timer
|
||||
* @param interval the amount of ticks between each {@code onTick()} call
|
||||
* on the callback object; must be positive
|
||||
* @param callback a callback object
|
||||
*/
|
||||
public StopwatchTimer(Plugin plugin, long interval, TimerCallback callback) {
|
||||
super(plugin, interval, callback);
|
||||
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a StopwatchTimer with the given tick interval.
|
||||
* <p>
|
||||
* This constructor leaves the timer in an inconsistent state until the
|
||||
* {@link #setCallback(TimerCallback)} method is called with a valid
|
||||
* callback object.
|
||||
*
|
||||
* @param plugin the plugin responsible for the timer
|
||||
* @param interval the amount of ticks between each {@code onTick()} call
|
||||
* on the callback object provided later; must be positive
|
||||
*/
|
||||
public StopwatchTimer(Plugin plugin, long interval) {
|
||||
this(plugin, interval, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the timer.
|
||||
* <p>
|
||||
* The timer will start ticking every {@code interval} ticks, as given in
|
||||
* the constructor, calling the {@code onTick()} method on the callback
|
||||
* on every tick.
|
||||
* <p>
|
||||
* The timer will continue ticking until manually stopped via the timer's
|
||||
* {@link #stop()} method.
|
||||
*/
|
||||
@Override
|
||||
public void start() {
|
||||
if (timer != null) {
|
||||
return;
|
||||
}
|
||||
callback.onStart();
|
||||
timer = new Timer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the timer.
|
||||
* <p>
|
||||
* This will call the {@code onStop()} method on the callback, and reset
|
||||
* the timer to a state in which calling the {@link #start()} method will
|
||||
* restart the timer.
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
if (timer == null) {
|
||||
return;
|
||||
}
|
||||
timer.stop();
|
||||
timer = null;
|
||||
callback.onStop();
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public synchronized boolean isRunning() {
|
||||
return timer != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal timer class for the actual legwork. The timer will reschedule
|
||||
* itself after every tick, if rescheduling is applicable. Furthermore,
|
||||
* the timer will auto-start on creation to avoid having to schedule it
|
||||
* from the {@link #start()} method.
|
||||
*/
|
||||
private class Timer implements Runnable {
|
||||
private BukkitTask task;
|
||||
|
||||
public Timer() {
|
||||
reschedule();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Tick
|
||||
callback.onTick();
|
||||
|
||||
// If stop() was called from onTick(), don't reschedule
|
||||
if (task != null) {
|
||||
reschedule();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void stop() {
|
||||
task.cancel();
|
||||
task = null;
|
||||
}
|
||||
|
||||
private synchronized void reschedule() {
|
||||
task = Bukkit.getScheduler().runTaskLater(plugin, this, interval);
|
||||
}
|
||||
}
|
||||
}
|
86
src/com/garbagemule/MobArena/util/timer/Timer.java
Normal file
86
src/com/garbagemule/MobArena/util/timer/Timer.java
Normal file
@ -0,0 +1,86 @@
|
||||
package com.garbagemule.MobArena.util.timer;
|
||||
|
||||
/**
|
||||
* Generic Timer interface for various implementations of timers.
|
||||
* <p>
|
||||
* The public facing methods of timers provide means of starting and stopping
|
||||
* the timers, as well as getting and setting tick intervals, and setting
|
||||
* callbacks post-construction.
|
||||
* <p>
|
||||
* A timer is expected to call methods on an underlying {@link TimerCallback}
|
||||
* object (when appropriate), which is provided either in the construction of
|
||||
* the timer, or via the {@link #setCallback(TimerCallback)} method after the
|
||||
* timer has been constructed with a null callback.
|
||||
* <p>
|
||||
* Conditions for callbacks:
|
||||
* <ul>
|
||||
* <li>When a timer is started via its {@link #start()} method, it calls
|
||||
* {@code onStart()} on the callback.
|
||||
* <li>If a timer is manually stopped, it calls {@code onStop()} on the
|
||||
* callback when its {@link #stop()} method is called.
|
||||
* <li>If a timer can "tick", it calls {@code onTick()} on the callback
|
||||
* every time it ticks.
|
||||
* <li>If a timer can finish ("run out"), it calls the {@code onFinish()}
|
||||
* method on the callback when it finishes.
|
||||
* </ul>
|
||||
* A timer must support manual stopping, i.e. it must be possible to stop a
|
||||
* timer, even if it can "run out". Ticking and finishing is optional, as it
|
||||
* may not always be relevant, depending on the implementation.
|
||||
*/
|
||||
public interface Timer {
|
||||
/**
|
||||
* Start the timer.
|
||||
*/
|
||||
public void start();
|
||||
|
||||
/**
|
||||
* Stop the timer.
|
||||
*/
|
||||
public void stop();
|
||||
|
||||
/**
|
||||
* Check if the timer is running.
|
||||
*
|
||||
* @return true, if the timer is currently running, false otherwise
|
||||
*/
|
||||
public boolean isRunning();
|
||||
|
||||
/**
|
||||
* Set the callback object of the timer.
|
||||
* <p>
|
||||
* This is a convenience method that allows for creating a callback that
|
||||
* references the timer. Due to Java's "local variable may not have been
|
||||
* initialized" rule, an anonymous callback cannot reference its timer
|
||||
* host, unless the host is a field or a final variable, and creating the
|
||||
* callback in the same statement as the timer means the timer has not
|
||||
* yet been initialized, according to Java. As such, if the callback
|
||||
* references the timer (e.g. to restart it or stop it prematurely), the
|
||||
* callback must be set with this method after creating the timer with
|
||||
* a null callback in the constructor.
|
||||
*
|
||||
* @param callback a callback object; must be non-null
|
||||
* @throws IllegalArgumentException if the callback is null
|
||||
* @throws IllegalStateException if the callback has already been set
|
||||
*/
|
||||
public void setCallback(TimerCallback callback);
|
||||
|
||||
/**
|
||||
* Get the tick interval of the timer.
|
||||
*
|
||||
* @return the tick interval of the timer
|
||||
*/
|
||||
public long getInterval();
|
||||
|
||||
/**
|
||||
* Set the tick interval of the timer.
|
||||
* <p>
|
||||
* The tick interval may be changed on-the-fly, but will not take effect
|
||||
* until after the next tick. As such, changing the tick interval after
|
||||
* the timer has been started without specifying the tick interval in the
|
||||
* constructor will have no effect.
|
||||
*
|
||||
* @param interval tick interval of the timer; must be positive
|
||||
* @throws IllegalArgumentException if the value is non-positive
|
||||
*/
|
||||
public void setInterval(long interval);
|
||||
}
|
39
src/com/garbagemule/MobArena/util/timer/TimerCallback.java
Normal file
39
src/com/garbagemule/MobArena/util/timer/TimerCallback.java
Normal file
@ -0,0 +1,39 @@
|
||||
package com.garbagemule.MobArena.util.timer;
|
||||
|
||||
public interface TimerCallback {
|
||||
/**
|
||||
* Called when the timer is started.
|
||||
* <p>
|
||||
* Note that this method is called before the timer is first scheduled,
|
||||
* which means the interval can be changed within this method prior to
|
||||
* the timer actually starting.
|
||||
*/
|
||||
public void onStart();
|
||||
|
||||
/**
|
||||
* Called when the timer ticks.
|
||||
* <p>
|
||||
* Ticks are implementation-specific. Refer to the documentation of the
|
||||
* specific timer for details.
|
||||
*/
|
||||
public void onTick();
|
||||
|
||||
/**
|
||||
* Called when the timer finishes.
|
||||
* <p>
|
||||
* A timer finishes when it "runs out", which is implementation-specific.
|
||||
* For example, the {@link CountdownTimer} finishes when it has counted
|
||||
* down to 0.
|
||||
*/
|
||||
public void onFinish();
|
||||
|
||||
/**
|
||||
* Called when the timer is stopped prematurely.
|
||||
* <p>
|
||||
* Stopping a timer prematurely is different from the timer naturally
|
||||
* running out, however some timers may never run out and must be
|
||||
* stopped manually, in which case this method is called instead of
|
||||
* the {@code onFinish()} method.
|
||||
*/
|
||||
public void onStop();
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.garbagemule.MobArena.util.timer;
|
||||
|
||||
/**
|
||||
* An empty implementation of the {@link TimerCallback} interface á la the
|
||||
* {@link java.awt.event.MouseAdapter} class in the Swing event framwork.
|
||||
*/
|
||||
public class TimerCallbackAdapter implements TimerCallback {
|
||||
@Override
|
||||
public void onStart() {}
|
||||
|
||||
@Override
|
||||
public void onTick() {}
|
||||
|
||||
@Override
|
||||
public void onFinish() {}
|
||||
|
||||
@Override
|
||||
public void onStop() {}
|
||||
}
|
Loading…
Reference in New Issue
Block a user