diff --git a/api/src/main/java/net/md_5/bungee/api/ProxyServer.java b/api/src/main/java/net/md_5/bungee/api/ProxyServer.java index 63904e4c..927e142b 100644 --- a/api/src/main/java/net/md_5/bungee/api/ProxyServer.java +++ b/api/src/main/java/net/md_5/bungee/api/ProxyServer.java @@ -13,6 +13,7 @@ import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.Server; import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.scheduler.TaskScheduler; public abstract class ProxyServer { @@ -229,4 +230,6 @@ public abstract class ProxyServer * @return the folder used to load plugin */ public abstract File getPluginsFolder(); + + public abstract TaskScheduler getScheduler(); } diff --git a/api/src/main/java/net/md_5/bungee/api/scheduler/ScheduledTask.java b/api/src/main/java/net/md_5/bungee/api/scheduler/ScheduledTask.java new file mode 100644 index 00000000..9e8dae07 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/scheduler/ScheduledTask.java @@ -0,0 +1,41 @@ +package net.md_5.bungee.api.scheduler; + +import java.util.concurrent.TimeUnit; +import net.md_5.bungee.api.plugin.Plugin; + +/** + * Represents a task scheduled for execution by the {@link TaskScheduler}. + */ +public interface ScheduledTask +{ + + /** + * Gets the unique ID of this task. + * + * @return this tasks ID + */ + int getId(); + + /** + * Return the plugin which scheduled this task for execution. + * + * @return the owning plugin + */ + Plugin getOwner(); + + /** + * Get the actual method which will be executed by this task. + * + * @return the {@link Runnable} behind this task + */ + Runnable getTask(); + + /** + * Get the delay in the specified unit before this task will next be + * executed. + * + * @param unit the unit to get the delay in + * @return the time before the next execution of this task + */ + long getDelay(TimeUnit unit); +} diff --git a/api/src/main/java/net/md_5/bungee/api/scheduler/TaskScheduler.java b/api/src/main/java/net/md_5/bungee/api/scheduler/TaskScheduler.java new file mode 100644 index 00000000..a5062ebd --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/scheduler/TaskScheduler.java @@ -0,0 +1,74 @@ +package net.md_5.bungee.api.scheduler; + +import java.util.concurrent.TimeUnit; +import net.md_5.bungee.api.plugin.Plugin; + +/** + * This interface represents a scheduler which may be used to queue, delay and + * execute tasks in an asynchronous fashion. + */ +public interface TaskScheduler +{ + + /** + * Cancel a task to prevent it from executing, or if its a repeating task, + * prevent its further execution. + * + * @param id the id of the task to cancel + */ + void cancel(int id); + + /** + * Cancel a task to prevent it from executing, or if its a repeating task, + * prevent its further execution. + * + * @param task the task to cancel + */ + void cancel(ScheduledTask task); + + /** + * Cancel all tasks owned by this plugin, this preventing them from being + * executed hereon in. + * + * @param plugin the plugin owning the tasks to be cancelled + * @return the number of tasks cancelled by this method + */ + int cancel(Plugin plugin); + + /** + * Schedule a task to be executed asynchronously. The task will commence + * running as soon as this method returns. + * + * @param owner the plugin owning this task + * @param task the task to run + * @return the scheduled task + */ + ScheduledTask runAsync(Plugin owner, Runnable task); + + /** + * Schedules a task to be executed asynchronously after the specified delay + * is up. + * + * @param owner the plugin owning this task + * @param task the task to run + * @param delay the delay before this task will be executed + * @param unit the unit in which the delay will be measured + * @return the scheduled task + */ + ScheduledTask schedule(Plugin owner, Runnable task, long delay, TimeUnit unit); + + /** + * Schedules a task to be executed asynchronously after the specified delay + * is up. The scheduled task will continue running at the specified + * interval. The interval will not begin to count down until the last task + * invocation is complete. + * + * @param owner the plugin owning this task + * @param task the task to run + * @param delay the delay in milliseconds before this task will be executed + * @param period the interval before subsequent executions of this task + * @param unit the unit in which the delay and period will be measured + * @return the scheduled task + */ + ScheduledTask schedule(Plugin owner, Runnable task, long delay, long period, TimeUnit unit); +} diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java index a87ace7f..58b8358e 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -1,5 +1,6 @@ package net.md_5.bungee; +import net.md_5.bungee.scheduler.BungeeScheduler; import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; @@ -38,6 +39,7 @@ import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.Server; import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.PluginManager; +import net.md_5.bungee.api.scheduler.TaskScheduler; import net.md_5.bungee.command.*; import net.md_5.bungee.config.YamlConfig; import net.md_5.bungee.netty.PipelineUtils; @@ -69,9 +71,7 @@ public class BungeeCord extends ProxyServer /** * Thread pool. */ - public final MultithreadEventLoopGroup eventLoops = new NioEventLoopGroup( - Runtime.getRuntime().availableProcessors() * 2, - new ThreadFactoryBuilder().setNameFormat( "Netty IO Thread - %1$d" ).build() ); + public final MultithreadEventLoopGroup eventLoops = new NioEventLoopGroup( 0, new ThreadFactoryBuilder().setNameFormat( "Netty IO Thread - %1$d" ).build() ); /** * locations.yml save thread. */ @@ -104,6 +104,8 @@ public class BungeeCord extends ProxyServer private final Collection pluginChannels = new HashSet<>(); @Getter private final File pluginsFolder = new File( "plugins" ); + @Getter + private final TaskScheduler scheduler = new BungeeScheduler(); { @@ -252,10 +254,12 @@ public class BungeeCord extends ProxyServer reconnectHandler.save(); saveThread.cancel(); + // TODO: Fix this shit getLogger().info( "Disabling plugins" ); for ( Plugin plugin : pluginManager.getPlugins() ) { plugin.onDisable(); + getScheduler().cancel( plugin ); } getLogger().info( "Thank you and goodbye" ); diff --git a/proxy/src/main/java/net/md_5/bungee/scheduler/BungeeScheduler.java b/proxy/src/main/java/net/md_5/bungee/scheduler/BungeeScheduler.java new file mode 100644 index 00000000..d05cb8c6 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/scheduler/BungeeScheduler.java @@ -0,0 +1,77 @@ +package net.md_5.bungee.scheduler; + +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import gnu.trove.TCollections; +import gnu.trove.iterator.TIntObjectIterator; +import gnu.trove.map.TIntObjectMap; +import gnu.trove.map.hash.TIntObjectHashMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.scheduler.ScheduledTask; +import net.md_5.bungee.api.scheduler.TaskScheduler; + +public class BungeeScheduler implements TaskScheduler +{ + + private final AtomicInteger taskCounter = new AtomicInteger(); + private final TIntObjectMap tasks = TCollections.synchronizedMap( new TIntObjectHashMap() ); + private final ScheduledExecutorService executors = new ScheduledThreadPoolExecutor( 0, new ThreadFactoryBuilder().setNameFormat( "Bungee Scheduler Thread - %1$d" ).build() ); + + @Override + public void cancel(int id) + { + cancel( tasks.remove( id ) ); + } + + @Override + public void cancel(ScheduledTask task) + { + Preconditions.checkArgument( task instanceof BungeeTask, "Don't know how to handle task %s", task ); + tasks.remove( task.getId() ).getFuture().cancel( false ); + } + + @Override + public int cancel(Plugin plugin) + { + int cancelled = 0; + for ( TIntObjectIterator iter = tasks.iterator(); iter.hasNext(); ) + { + BungeeTask task = iter.value(); + if ( task.getOwner() == plugin ) + { + task.getFuture().cancel( false ); + iter.remove(); + cancelled++; + } + } + return cancelled; + } + + @Override + public ScheduledTask runAsync(Plugin owner, Runnable task) + { + return schedule( owner, task, 0, TimeUnit.MILLISECONDS ); + } + + @Override + public ScheduledTask schedule(Plugin owner, Runnable task, long delay, TimeUnit unit) + { + return prepare( owner, task ).setFuture( executors.schedule( task, delay, unit ) ); + } + + @Override + public ScheduledTask schedule(Plugin owner, Runnable task, long delay, long period, TimeUnit unit) + { + return prepare( owner, task ).setFuture( executors.scheduleWithFixedDelay( task, delay, period, unit ) ); + } + + private BungeeTask prepare(Plugin owner, Runnable task) + { + int id = taskCounter.getAndIncrement(); + return tasks.put( id, new BungeeTask( taskCounter.getAndIncrement(), owner, task ) ); + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/scheduler/BungeeTask.java b/proxy/src/main/java/net/md_5/bungee/scheduler/BungeeTask.java new file mode 100644 index 00000000..c132bf0e --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/scheduler/BungeeTask.java @@ -0,0 +1,32 @@ +package net.md_5.bungee.scheduler; + +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import lombok.AccessLevel; +import lombok.Data; +import lombok.Setter; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.scheduler.ScheduledTask; + +@Data +public class BungeeTask implements ScheduledTask +{ + + private final int id; + private final Plugin owner; + private final Runnable task; + @Setter(AccessLevel.NONE) + private ScheduledFuture future; + + @Override + public long getDelay(TimeUnit unit) + { + return future.getDelay( unit ); + } + + BungeeTask setFuture(ScheduledFuture future) + { + this.future = future; + return this; + } +}