package com.supaham.commons.bungee; import com.google.common.base.Preconditions; import com.supaham.commons.Pausable; import com.supaham.commons.state.State; import com.supaham.commons.state.Stateable; import net.md_5.bungee.api.scheduler.ScheduledTask; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * Represents a ticker task, a task that runs over a set interval. A {@link CommonTask} 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 ScheduledTask}. * * @see #CommonTask(CommonPlugin, long) * @see #CommonTask(CommonPlugin, long, long) * @see #CommonTask(CommonPlugin, long, Runnable) * @see #CommonTask(CommonPlugin, long, long, Runnable) * @since 0.3.6 */ public class CommonTask implements Runnable, Pausable, Stateable { private final CommonPlugin plugin; private final Runnable runnable; private long delay; private long interval; private TimeUnit unit; protected State state = State.STOPPED; private ScheduledTask 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 milliseconds). This is * equivalent to {@link #CommonTask(CommonPlugin, long, TimeUnit)} with the {@code unit} being * {@link TimeUnit#MILLISECONDS}. * * @param plugin plugin to own this task * @param delay delay (in milliseconds) before this task should initiate * * @see #CommonTask(CommonPlugin, long, TimeUnit) * @see #CommonTask(CommonPlugin, long, TimeUnit, Runnable) */ public CommonTask(@Nonnull CommonPlugin plugin, long delay) { this(plugin, delay, TimeUnit.MILLISECONDS); } /** * Constructs a new TickerTask that runs once after the given delay (in milliseconds). This is * equivalent to {@link #CommonTask(CommonPlugin, long, TimeUnit, Runnable)} with the {@code * unit} being {@link TimeUnit#MILLISECONDS}. * * @param plugin plugin to own this task * @param delay delay (in milliseconds) before this task should initiate * * @see #CommonTask(CommonPlugin, long, TimeUnit) * @see #CommonTask(CommonPlugin, long, TimeUnit, Runnable) */ public CommonTask(@Nonnull CommonPlugin plugin, long delay, Runnable runnable) { this(plugin, delay, TimeUnit.MILLISECONDS, runnable); } /** * Constructs a new TickerTask that runs once after the given delay. This is equivalent to * {@link #CommonTask(CommonPlugin, long, TimeUnit, Runnable)} with the last {@code runnable} * being null. * * @param plugin plugin to own this task * @param delay delay (in ticks) before this task should initiate * @param unit time unit the delay is measured in * * @see #CommonTask(CommonPlugin, long, long, TimeUnit) * @see #CommonTask(CommonPlugin, long, TimeUnit, Runnable) */ public CommonTask(@Nonnull CommonPlugin plugin, long delay, @Nonnull TimeUnit unit) { this(plugin, delay, unit, null); } /** * Constructs a new TickerTask that runs over a set interact after the given delay (in * milliseconds). This is equivalent to calling {@link #CommonTask(CommonPlugin, long, long, * TimeUnit, 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 milliseconds) before this task should initiate * @param interval interval (in milliseconds) between each run * * @see #CommonTask(CommonPlugin, long, long, TimeUnit) * @see #CommonTask(CommonPlugin, long, long, TimeUnit, Runnable) */ public CommonTask(@Nonnull CommonPlugin plugin, long delay, long interval) { this(plugin, delay, interval, TimeUnit.MILLISECONDS, null); } /** * Constructs a new TickerTask that runs over a set interact after the given delay (in ticks). * This is equivalent to calling {@link #CommonTask(CommonPlugin, long, long, TimeUnit, * 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 #CommonTask(CommonPlugin, long, long, Runnable) */ public CommonTask(@Nonnull CommonPlugin plugin, long delay, long interval, @Nonnull TimeUnit unit) { this(plugin, delay, interval, unit, 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 #CommonTask(CommonPlugin, long) */ public CommonTask(@Nonnull CommonPlugin plugin, long delay, TimeUnit unit, @Nullable Runnable runnable) { this(plugin, delay, -1, unit, 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 CommonTask(@Nonnull CommonPlugin plugin, long delay, long interval, @Nullable Runnable runnable) { this(plugin, delay, interval, TimeUnit.MILLISECONDS, 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 CommonTask(@Nonnull CommonPlugin plugin, long delay, long interval, @Nonnull TimeUnit unit, @Nullable Runnable runnable) { Preconditions.checkNotNull(plugin, "plugin cannot be null."); this.plugin = plugin; this.delay = delay; setInterval(interval); this.unit = unit; this.runnable = runnable == null ? this : runnable; } /** * 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 CommonTask}. 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; } this.task = plugin.getProxy().getScheduler().schedule(plugin.getBungeePlugin(), new Runnable() { @Override public void run() { _run(); } }, this.delay, this.interval, this.unit); this.paused = false; return true; } /** * Stops this {@link CommonTask}. 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 CommonTask}. 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 CommonTask}. 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 CommonPlugin 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 ScheduledTask getTask() { return task; } public long getLastTickMillis() { return lastTickMillis; } /** * Sets whether this {@link CommonTask} 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; } }