diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 5f6466b6..e30fe521 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -29,6 +29,7 @@ import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.events.ConnectionSide; import com.comphenix.protocol.events.MonitorAdapter; import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.injector.DelayedSingleTask; import com.comphenix.protocol.injector.PacketFilterManager; import com.comphenix.protocol.metrics.Statistics; import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; @@ -58,13 +59,17 @@ public class ProtocolLibrary extends JavaPlugin { private int tickCounter = 0; private static final int ASYNC_PACKET_DELAY = 1; + // Used to unhook players after a delay + private DelayedSingleTask unhookTask; + // Used for debugging private boolean debugListener; @Override public void onLoad() { logger = getLoggerSafely(); - protocolManager = new PacketFilterManager(getClassLoader(), getServer(), logger); + unhookTask = new DelayedSingleTask(this); + protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, logger); } @Override @@ -181,6 +186,7 @@ public class ProtocolLibrary extends JavaPlugin { asyncPacketTask = -1; } + unhookTask.close(); protocolManager.close(); protocolManager = null; statistisc = null; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedSingleTask.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedSingleTask.java new file mode 100644 index 00000000..b44de2ee --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedSingleTask.java @@ -0,0 +1,132 @@ +package com.comphenix.protocol.injector; + +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; + +/** + * Represents a single delayed task. + * + * @author Kristian + */ +public class DelayedSingleTask { + + protected int taskID = -1; + protected Plugin plugin; + protected BukkitScheduler scheduler; + protected boolean closed; + + /** + * Create a single task scheduler. + * @param plugin - owner plugin. + */ + public DelayedSingleTask(Plugin plugin) { + this.plugin = plugin; + this.scheduler = plugin.getServer().getScheduler(); + } + + /** + * Create a single task scheduler. + * @param plugin - owner plugin. + * @param scheduler - specialized scheduler. + */ + public DelayedSingleTask(Plugin plugin, BukkitScheduler scheduler) { + this.plugin = plugin; + this.scheduler = scheduler; + } + + /** + * Schedule a single task for execution. + *

+ * Any previously scheduled task will be automatically cancelled. + *

+ * Note that a tick delay of zero will execute the task immediately. + * + * @param ticksDelay - number of ticks before the task is executed. + * @param task - the task to schedule. + * @return TRUE if the task was successfully scheduled or executed, FALSE otherwise. + */ + public boolean schedule(long ticksDelay, Runnable task) { + if (ticksDelay < 0) + throw new IllegalArgumentException("Tick delay cannot be negative."); + if (task == null) + throw new IllegalArgumentException("task cannot be NULL"); + if (closed) + return false; + + // Special case + if (ticksDelay == 0) { + task.run(); + return true; + } + + // Boilerplate, boilerplate + final Runnable dispatch = task; + + // Don't run multiple tasks! + cancel(); + taskID = scheduler.scheduleSyncDelayedTask(plugin, new Runnable() { + @Override + public void run() { + dispatch.run(); + taskID = -1; + } + }, ticksDelay); + + return isRunning(); + } + + /** + * Whether or not a future task is scheduled to be executed. + * @return TRUE if a current task has been scheduled for execution, FALSE otherwise. + */ + public boolean isRunning() { + return taskID >= 0; + } + + /** + * Cancel a future task from being executed. + * @return TRUE if a task was cancelled, FALSE otherwise. + */ + public boolean cancel() { + if (isRunning()) { + scheduler.cancelTask(taskID); + taskID = -1; + return true; + } else { + return false; + } + } + + /** + * Retrieve the raw task ID. + * @return Raw task ID, or negative one if no task has been scheduled. + */ + public int getTaskID() { + return taskID; + } + + /** + * Retrieve the plugin this task belongs to. + * @return The plugin scheduling the current taks. + */ + public Plugin getPlugin() { + return plugin; + } + + /** + * Stop the current task and all future tasks scheduled by this instance. + */ + public synchronized void close() { + if (!closed) { + cancel(); + plugin = null; + scheduler = null; + closed = true; + } + } + + @Override + protected void finalize() throws Throwable { + close(); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index 2ce8ab6a..2bd87891 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -90,6 +90,13 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok NETWORK_SERVER_OBJECT; } + // The amount of time to wait until we actually unhook every player + private static final int TICKS_PER_SECOND = 20; + private static final int UNHOOK_DELAY = 5 * TICKS_PER_SECOND; + + // Delayed unhook + private DelayedSingleTask unhookTask; + // Create a concurrent set private Set packetListeners = Collections.newSetFromMap(new ConcurrentHashMap()); @@ -129,13 +136,23 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok /** * Only create instances of this class if protocol lib is disabled. + * @param unhookTask */ - public PacketFilterManager(ClassLoader classLoader, Server server, Logger logger) { + public PacketFilterManager(ClassLoader classLoader, Server server, DelayedSingleTask unhookTask, Logger logger) { if (logger == null) throw new IllegalArgumentException("logger cannot be NULL."); if (classLoader == null) throw new IllegalArgumentException("classLoader cannot be NULL."); + // Just boilerplate + final DelayedSingleTask finalUnhookTask = unhookTask; + + // References + this.unhookTask = unhookTask; + this.server = server; + this.classLoader = classLoader; + this.logger = logger; + // Used to determine if injection is needed Predicate isInjectionNecessary = new Predicate() { @Override @@ -144,17 +161,15 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok if (phase.hasLogin()) result &= getPhaseLoginCount() > 0; + // Note that we will still hook players if the unhooking has been delayed if (phase.hasPlaying()) - result &= getPhasePlayingCount() > 0; + result &= getPhasePlayingCount() > 0 || finalUnhookTask.isRunning(); return result; } }; try { - // Initialize values - this.server = server; - this.classLoader = classLoader; - this.logger = logger; + // Initialize injection mangers this.playerInjection = new PlayerInjectionHandler(classLoader, logger, isInjectionNecessary, this, server); this.packetInjector = new PacketInjector(classLoader, this, playerInjection); this.asyncFilterManager = new AsyncFilterManager(logger, server.getScheduler(), this); @@ -257,8 +272,12 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // We may have to inject into every current player if (phase.hasPlaying()) { if (phasePlayingCount.incrementAndGet() == 1) { - // Inject our hook into already existing players - initializePlayers(server.getOnlinePlayers()); + // If we're about to uninitialize every player, cancel that instead + if (unhookTask.isRunning()) + unhookTask.cancel(); + else + // Inject our hook into already existing players + initializePlayers(server.getOnlinePlayers()); } } } @@ -274,8 +293,14 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // We may have to inject into every current player if (phase.hasPlaying()) { if (phasePlayingCount.decrementAndGet() == 0) { - // Inject our hook into already existing players - uninitializePlayers(server.getOnlinePlayers()); + // Schedule unhooking in the future + unhookTask.schedule(UNHOOK_DELAY, new Runnable() { + @Override + public void run() { + // Inject our hook into already existing players + uninitializePlayers(server.getOnlinePlayers()); + } + }); } } }