package com.supaham.commons.bukkit; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.base.Preconditions; import com.supaham.commons.Pausable; import com.supaham.commons.state.State; import com.supaham.commons.state.Stateable; import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitTask; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * Represents a ticker task, a task that runs over a set interval. A {@link TickerTask} can be * stopped or started at any point in time. As well as paused or resumed at any point in time * (assuming the task isn't stopped). This class doesn't implement the actual task call handling, * it makes use of Bukkit's {@link BukkitTask}. * * @see #TickerTask(Plugin, long) * @see #TickerTask(Plugin, long, long) * @see #TickerTask(Plugin, long, Runnable) * @see #TickerTask(Plugin, long, long, Runnable) * @since 0.1 */ public class TickerTask implements Runnable, Pausable, Stateable { private final Plugin plugin; private final Runnable runnable; private long delay; private long interval; private boolean async; protected State state = State.STOPPED; private BukkitTask task; private long lastTickMillis; private boolean paused = true; private long totalTicks; private long currentTick; /** * Constructs a new TickerTask that runs once after the given delay (in ticks). This is * equivalent to {@link #TickerTask(Plugin, long, long)} with the last {@code runnable} being -1. * * @param plugin plugin to own this task * @param delay delay (in ticks) before this task should initiate * * @see #TickerTask(Plugin, long, long) * @see #TickerTask(Plugin, long, Runnable) */ public TickerTask(@Nonnull Plugin plugin, long delay) { this(plugin, delay, null); } /** * Constructs a new TickerTask that runs over a set interact after the given delay (in ticks). * This is equivalent to calling {@link #TickerTask(Plugin, long, Runnable)} with the * {@code runnable} as null. * * <b>Note:</b> If the interval is -1 this task will only run once. * * @param plugin plugin to own this task * @param delay delay (in ticks) before this task should initiate * @param interval interval between each run * * @see #TickerTask(Plugin, long, long, Runnable) */ public TickerTask(@Nonnull Plugin plugin, long delay, long interval) { this(plugin, delay, interval, null); } /** * Constructs a new TickerTask that runs once after the given delay (in ticks). This runnable * constructor exists solely for 1.8 support, providing convenience through lambda usage. * * @param plugin plugin to own this task * @param delay delay (in ticks) before this task should initiate * @param runnable runnable to use for this execution * * @see #TickerTask(Plugin, long) */ public TickerTask(@Nonnull Plugin plugin, long delay, @Nullable Runnable runnable) { this(plugin, delay, -1, runnable); } /** * Constructs a new TickerTask that runs over a set interact after the given delay (in ticks). * * <b>Note:</b> If the interval is -1 this task will only run once. * * @param plugin plugin to own this task * @param delay delay (in ticks) before this task should initiate */ public TickerTask(@Nonnull Plugin plugin, long delay, long interval, @Nullable Runnable runnable) { checkNotNull(plugin, "plugin cannot be null."); this.plugin = plugin; this.delay = delay; setInterval(interval); this.runnable = runnable == null ? this : runnable; } /** * Creates a new {@link TickerTask} by shallow copying another {@link TickerTask}. * * @param task task to clone */ public TickerTask(@Nonnull TickerTask task) { Preconditions.checkNotNull(task, "task cannot be null."); this.plugin = task.plugin; this.runnable = task.runnable; this.delay = task.delay; this.interval = task.interval; this.async = task.async; this.state = task.state; this.task = task.task; this.lastTickMillis = task.lastTickMillis; this.paused = task.paused; this.totalTicks = task.totalTicks; this.currentTick = task.currentTick; } /** * This method does not need to be overridden if a {@link Runnable} was passed to the * constructor. If no runnable was passed to the constructor, this task will do nothing. */ @Override public void run() {} private void _run() { totalTicks++; if (isPaused()) { return; } currentTick++; try { this.runnable.run(); } catch (Exception e) { e.printStackTrace(); } this.lastTickMillis = System.currentTimeMillis(); } /** * Starts this {@link TickerTask} synchronously. If this task is already started the call is * terminated. * * @return true if the task's state was changed to started */ public boolean start() { if (isStarted()) { return false; } Runnable runnable = new Runnable() { @Override public void run() { _run(); } }; if (isAsync()) { // Asynchronous this.task = plugin.getServer().getScheduler() .runTaskTimerAsynchronously(plugin, runnable, delay, interval); } else { // Synchronous this.task = plugin.getServer().getScheduler().runTaskTimer(plugin, runnable, delay, interval); } this.paused = false; return true; } /** * Starts this {@link TickerTask} asynchronously. If this task is already started the call is * terminated. * * @return true if the task's state was changed to started */ public boolean startAsync() { if (isStarted()) { return false; } setAsync(); start(); return start(); } /** * Starts this {@link TickerTask} synchronously. If this task is already started the call is * terminated. * * @return true if the task's state was changed to started */ public boolean startSync() { if (isStarted()) { return false; } setAsync(false); return start(); } /** * Stops this {@link TickerTask}. If this task is already stopped the call is terminated. * * @return true if the task's state was changed to stopped */ public boolean stop() { if (!isStarted()) { return false; } this.task.cancel(); this.task = null; this.paused = true; return true; } @Override public boolean isPaused() { return paused; } /** * Pauses this {@link TickerTask}. If this task is already paused the call is terminated. * * @return true if this task's state was changed to paused */ @Override public boolean pause() { if (!isStarted() || this.paused) { return false; } this.paused = true; return true; } /** * Resumes this {@link TickerTask}. If this task is not paused the call is terminated. * * @return true if this task's state was changed to resumed */ @Override public boolean resume() { if (!isStarted() || !this.paused) { return false; } this.paused = false; return true; } @Nonnull @Override public State getState() { return this.state; } @Override public boolean setState(@Nonnull State state) throws UnsupportedOperationException { Preconditions.checkNotNull(state, "state cannot be null."); State old = this.state; boolean change = this.state.isIdle() != state.isIdle(); if (change) { this.state = state; switch (state) { case PAUSED: pause(); break; case ACTIVE: if (old == State.PAUSED) { // Only resume if it was previously paused resume(); } else { start(); } break; case STOPPED: stop(); break; } } return change; } public boolean isStarted() { return this.task != null; } public Plugin getPlugin() { return plugin; } public long getDelay() { return delay; } public void setDelay(long delay) { this.delay = delay; } public long getInterval() { return interval; } public void setInterval(long interval) { this.interval = Math.max(interval, -1); } public boolean isAsync() { return async; } public TickerTask setAsync() { return setAsync(true); } public TickerTask setAsync(boolean async) { Preconditions.checkState(!isStarted(), "task is already started."); this.async = async; return this; } public BukkitTask getTask() { return task; } public long getLastTickMillis() { return lastTickMillis; } /** * Sets whether this {@link TickerTask} should be paused. If {@code paused} is true it would be * equivalent to calling {@link #pause()}, otherwise {@link #resume()}. * * @param paused whether to pause the task * * @return whether any action was taken, a case where this would return false if {@code paused} is * true and this task is already paused * * @see #pause() * @see #resume() */ public boolean setPaused(boolean paused) { return paused ? pause() : resume(); } public long getTotalTicks() { return totalTicks; } public long getCurrentTick() { return currentTick; } }