From af2b9fb2b5148610ef67de705c6c67212073c1a4 Mon Sep 17 00:00:00 2001 From: asofold Date: Sat, 8 Sep 2012 23:34:49 +0200 Subject: [PATCH] Bleeding: Fully rework asynchronous actions execution. --- .../neatmonster/nocheatplus/NoCheatPlus.java | 20 --- .../neatmonster/nocheatplus/checks/Check.java | 123 ++++++------------ .../checks/DelayedActionsExecution.java | 62 +++++++++ .../checks/ExecuteActionsEvent.java | 88 ------------- .../nocheatplus/checks/ViolationData.java | 25 ---- .../nocheatplus/checks/chat/Color.java | 2 +- .../nocheatplus/checks/chat/GlobalChat.java | 2 +- .../nocheatplus/checks/chat/NoPwnage.java | 6 +- .../nocheatplus/hooks/APIUtils.java | 2 + .../nocheatplus/hooks/NCPHook.java | 5 +- .../nocheatplus/hooks/NCPHookManager.java | 41 +++--- .../nocheatplus/utilities/TickTask.java | 45 +++++-- 12 files changed, 173 insertions(+), 248 deletions(-) create mode 100644 src/fr/neatmonster/nocheatplus/checks/DelayedActionsExecution.java delete mode 100644 src/fr/neatmonster/nocheatplus/checks/ExecuteActionsEvent.java diff --git a/src/fr/neatmonster/nocheatplus/NoCheatPlus.java b/src/fr/neatmonster/nocheatplus/NoCheatPlus.java index 72bafe98..3c307abd 100644 --- a/src/fr/neatmonster/nocheatplus/NoCheatPlus.java +++ b/src/fr/neatmonster/nocheatplus/NoCheatPlus.java @@ -20,7 +20,6 @@ import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.java.JavaPlugin; import fr.neatmonster.nocheatplus.checks.CheckType; -import fr.neatmonster.nocheatplus.checks.ExecuteActionsEvent; import fr.neatmonster.nocheatplus.checks.Workarounds; import fr.neatmonster.nocheatplus.checks.blockbreak.BlockBreakListener; import fr.neatmonster.nocheatplus.checks.blockinteract.BlockInteractListener; @@ -223,25 +222,6 @@ public class NoCheatPlus extends JavaPlugin implements Listener { System.out.println("[NoCheatPlus] Version " + getDescription().getVersion() + " is enabled."); } - /** - * This event handler is used to execute the actions when a violation is detected. - * - * @param event - * the event handled - */ - @EventHandler( - priority = EventPriority.LOWEST) - final void onExecuteActions(final ExecuteActionsEvent event) { - /* - * _____ _ _ _ _ - * | ____|_ _____ ___ _ _| |_ ___ / \ ___| |_(_) ___ _ __ ___ - * | _| \ \/ / _ \/ __| | | | __/ _ \ / _ \ / __| __| |/ _ \| '_ \/ __| - * | |___ > < __/ (__| |_| | || __/ / ___ \ (__| |_| | (_) | | | \__ \ - * |_____/_/\_\___|\___|\__,_|\__\___| /_/ \_\___|\__|_|\___/|_| |_|___/ - */ - event.executeActions(); - } - public void onPlayerJoinLow(final PlayerJoinEvent event) { /* * ____ _ _ _ diff --git a/src/fr/neatmonster/nocheatplus/checks/Check.java b/src/fr/neatmonster/nocheatplus/checks/Check.java index 401f4280..985a773c 100644 --- a/src/fr/neatmonster/nocheatplus/checks/Check.java +++ b/src/fr/neatmonster/nocheatplus/checks/Check.java @@ -3,16 +3,14 @@ package fr.neatmonster.nocheatplus.checks; import java.util.HashMap; import java.util.Map; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import fr.neatmonster.nocheatplus.actions.Action; import fr.neatmonster.nocheatplus.actions.ParameterName; import fr.neatmonster.nocheatplus.actions.types.ActionList; import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager; import fr.neatmonster.nocheatplus.hooks.NCPHookManager; -import fr.neatmonster.nocheatplus.metrics.MetricsData; import fr.neatmonster.nocheatplus.players.ExecutionHistory; +import fr.neatmonster.nocheatplus.utilities.TickTask; /* * MM'""""'YMM dP dP @@ -56,62 +54,6 @@ public abstract class Check { public Check(final CheckType type) { this.type = type; } - - /** - * Convenience method. - * - * @param player - * the player - * @param vL - * the violation level - * @param addedVL - * the violation level added - * @param actions - * the actions - * @return true, if the event should be cancelled - */ - protected boolean executeActions(final Player player, final double vL, final double addedVL, - final ActionList actions) { - return executeActions(new ViolationData(this, player, vL, addedVL, actions)); - } - - /** - * Execute some actions for the specified player. - * - * @param violationData - * the violation data - * @return true, if the event should be cancelled - */ - protected boolean executeActions(final ViolationData violationData) { - ViolationHistory.getHistory(violationData.player).log(getClass().getName(), violationData.addedVL); - try { - // Check a bypass permission. - if (violationData.bypassPermission != null) - if (violationData.player.hasPermission(violationData.bypassPermission)) - return false; - - // Dispatch the VL processing to the hook manager. - if (NCPHookManager.shouldCancelVLProcessing(violationData)) - // One of the hooks has decided to cancel the VL processing, return false. - return false; - - // Add this failed check to the Metrics data. - MetricsData.addFailed(type); - - final long time = System.currentTimeMillis() / 1000L; - boolean cancel = false; - for (final Action action : violationData.getActions()) - if (getHistory(violationData.player).executeAction(violationData, action, time)) - // The execution history said it really is time to execute the action, find out what it is and do - // what is needed. - if (action.execute(violationData)) cancel = true; - - return cancel; - } catch (final Exception e) { - e.printStackTrace(); - } - return false; - } /** * Execute actions, possibly thread safe according to the isMainThread flag.
@@ -127,19 +69,14 @@ public abstract class Check { * if the thread the main thread * @return true, if successful */ - public final boolean executeActionsThreadSafe(final Player player, final double VL, final double VLAdded, - final ActionList actions, final boolean isMainThread) { - final boolean cancel; - if (isMainThread) - cancel = executeActions(player, VL, VLAdded, actions); - else - // Permission check is done in the main thread. - cancel = executeActionsThreadSafe(player, VL, VLAdded, actions, type.getPermission()); - return cancel; + public boolean executeActions(final Player player, final double vL, final double addedVL, + final ActionList actions, boolean isMainThread) { + // Sync it into the main thread by using an event. + return executeActions(new ViolationData(this, player, vL, addedVL, actions), isMainThread); } /** - * Execute actions in a thread safe manner. + * Convenience method: Execute actions from the main thread only. * * @param player * the player @@ -149,27 +86,49 @@ public abstract class Check { * the violation level added * @param actions * the actions - * @param bypassPermission - * the bypass permission * @return true, if the event should be cancelled */ - public boolean executeActionsThreadSafe(final Player player, final double vL, final double addedVL, - final ActionList actions, final String bypassPermission) { - // Sync it into the main thread by using an event. - return executeActionsThreadSafe(new ViolationData(this, player, vL, addedVL, actions, bypassPermission)); + protected boolean executeActions(final Player player, final double vL, final double addedVL, + final ActionList actions) { + return executeActions(new ViolationData(this, player, vL, addedVL, actions), true); } - + /** - * Execute actions in a thread safe manner. + * Execute some actions for the specified player, only for the main thread. * * @param violationData * the violation data - * @return true, if if the event should be cancelled + * @return true, if the event should be cancelled */ - public boolean executeActionsThreadSafe(final ViolationData violationData) { - final ExecuteActionsEvent event = new ExecuteActionsEvent(violationData); - Bukkit.getPluginManager().callEvent(event); - return event.getCancel(); + protected boolean executeActions(final ViolationData violationData){ + return executeActions(violationData, true); + } + + /** + * Execute some actions for the specified player. + * + * @param violationData + * the violation data + * @param isMainThread If this is called from within the main thread. If true, this must really be the main thread and not from synchronized code coming form another thread. + * @return true, if the event should be cancelled + */ + protected boolean executeActions(final ViolationData violationData, final boolean isMainThread) { + + // Dispatch the VL processing to the hook manager (now thread safe). + if (NCPHookManager.shouldCancelVLProcessing(violationData)) + // One of the hooks has decided to cancel the VL processing, return false. + return false; + + final DelayedActionsExecution delayedActions = new DelayedActionsExecution(violationData); + + if (isMainThread) return delayedActions.execute(); + else{ + TickTask.requestActionsExecution(delayedActions); + return delayedActions.hasCancel(); + } + + // (Design change: Permission checks are moved to cached permissions, lazily updated.) + } /** diff --git a/src/fr/neatmonster/nocheatplus/checks/DelayedActionsExecution.java b/src/fr/neatmonster/nocheatplus/checks/DelayedActionsExecution.java new file mode 100644 index 00000000..d0babae1 --- /dev/null +++ b/src/fr/neatmonster/nocheatplus/checks/DelayedActionsExecution.java @@ -0,0 +1,62 @@ +package fr.neatmonster.nocheatplus.checks; + +import fr.neatmonster.nocheatplus.actions.Action; +import fr.neatmonster.nocheatplus.actions.types.CancelAction; +import fr.neatmonster.nocheatplus.metrics.MetricsData; + +/** + * For scheduling actions execution. This does not check the NCPHookManager. + *
+ * Not put to ViolationData itself for the possibility of adding other data (might be considered though). + * @author mc_dev + * + */ +public class DelayedActionsExecution { + + protected final ViolationData violationData; + protected final Action[] actions; + + public DelayedActionsExecution(final ViolationData violationData) { + this.violationData = violationData; + actions = violationData.getActions(); + } + + /** + * Execute actions and return if cancel. + * @return + */ + public boolean execute(){ + try { + + ViolationHistory.getHistory(violationData.player).log(getClass().getName(), violationData.addedVL); + + // Add this failed check to the Metrics data. + MetricsData.addFailed(violationData.check.type); + + final long time = System.currentTimeMillis() / 1000L; + boolean cancel = false; + for (final Action action : violationData.getActions()) + if (Check.getHistory(violationData.player).executeAction(violationData, action, time)) + // The execution history said it really is time to execute the action, find out what it is and do + // what is needed. + if (action.execute(violationData)) cancel = true; + + return cancel; + } catch (final Exception e) { + e.printStackTrace(); + // On exceptions cancel events. + return true; + } + } + + /** + * Check if the actions contain a cancel. + * @return + */ + public boolean hasCancel(){ + for (final Action action : actions){ + if (action instanceof CancelAction) return true; + } + return false; + } +} diff --git a/src/fr/neatmonster/nocheatplus/checks/ExecuteActionsEvent.java b/src/fr/neatmonster/nocheatplus/checks/ExecuteActionsEvent.java deleted file mode 100644 index 13e4ea99..00000000 --- a/src/fr/neatmonster/nocheatplus/checks/ExecuteActionsEvent.java +++ /dev/null @@ -1,88 +0,0 @@ -package fr.neatmonster.nocheatplus.checks; - -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; - -/* - * MM""""""""`M dP MMP"""""""MM dP oo - * MM mmmmmmmM 88 M' .mmmm MM 88 - * M` MMMM dP. .dP .d8888b. .d8888b. dP dP d8888P .d8888b. M `M .d8888b. d8888P dP .d8888b. 88d888b. .d8888b. - * MM MMMMMMMM `8bd8' 88ooood8 88' `"" 88 88 88 88ooood8 M MMMMM MM 88' `"" 88 88 88' `88 88' `88 Y8ooooo. - * MM MMMMMMMM .d88b. 88. ... 88. ... 88. .88 88 88. ... M MMMMM MM 88. ... 88 88 88. .88 88 88 88 - * MM .M dP' `dP `88888P' `88888P' `88888P' dP `88888P' M MMMMM MM `88888P' dP dP `88888P' dP dP `88888P' - * MMMMMMMMMMMM MMMMMMMMMMMM - * - * MM""""""""`M dP - * MM mmmmmmmM 88 - * M` MMMM dP .dP .d8888b. 88d888b. d8888P - * MM MMMMMMMM 88 d8' 88ooood8 88' `88 88 - * MM MMMMMMMM 88 .88' 88. ... 88 88 88 - * MM .M 8888P' `88888P' dP dP dP - * MMMMMMMMMMMM - */ -/** - * This event is to be fired to execute actions in the main thread. - * - * @author asofold - */ -public class ExecuteActionsEvent extends Event { - - /** The list of the handlers of this event. */ - private static final HandlerList handlers = new HandlerList(); - - /** - * Return the list of all the handlers of this event. - * - * @return the handler list - */ - public static HandlerList getHandlerList() { - return handlers; - } - - /** The violation data. */ - private final ViolationData violationData; - - /** If the actions have been executed already. */ - private boolean actionsExecuted = false; - - /** If the event is cancelled or not. */ - private boolean cancel = false; - - /** - * Instantiates a new execute actions event. - * - * @param violationData - * the violation data - */ - public ExecuteActionsEvent(final ViolationData violationData) { - this.violationData = violationData; - } - - /** - * Execute the actions. - */ - public void executeActions() { - if (actionsExecuted) - return; - cancel = violationData.check.executeActions(violationData); - actionsExecuted = true; - } - - /** - * Return if the event is cancelled. - * - * @return the cancellation state - */ - public boolean getCancel() { - return cancel; - } - - /* (non-Javadoc) - * @see org.bukkit.event.Event#getHandlers() - */ - @Override - public HandlerList getHandlers() { - return handlers; - } - -} diff --git a/src/fr/neatmonster/nocheatplus/checks/ViolationData.java b/src/fr/neatmonster/nocheatplus/checks/ViolationData.java index 4fff016b..34b2ce22 100644 --- a/src/fr/neatmonster/nocheatplus/checks/ViolationData.java +++ b/src/fr/neatmonster/nocheatplus/checks/ViolationData.java @@ -28,9 +28,6 @@ public class ViolationData { /** The violation level added. */ public final double addedVL; - /** The bypassing permission. */ - public final String bypassPermission; - /** The check. */ public final Check check; @@ -56,33 +53,11 @@ public class ViolationData { */ public ViolationData(final Check check, final Player player, final double vL, final double addedVL, final ActionList actions) { - this(check, player, vL, addedVL, actions, null); - } - - /** - * Instantiates a new violation data. - * - * @param check - * the check - * @param player - * the player - * @param vL - * the violation level - * @param addedVL - * the violation level added - * @param actions - * the actions - * @param bypassPermission - * the permission to bypass the execution, if not null - */ - public ViolationData(final Check check, final Player player, final double vL, final double addedVL, - final ActionList actions, final String bypassPermission) { this.check = check; this.player = player; this.vL = vL; this.addedVL = addedVL; this.actions = actions; - this.bypassPermission = bypassPermission; } /** diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/Color.java b/src/fr/neatmonster/nocheatplus/checks/chat/Color.java index a0599c65..b20a7e1c 100644 --- a/src/fr/neatmonster/nocheatplus/checks/chat/Color.java +++ b/src/fr/neatmonster/nocheatplus/checks/chat/Color.java @@ -50,7 +50,7 @@ public class Color extends AsyncCheck { data.colorVL++; // Find out if we need to remove the colors or not. - if (executeActionsThreadSafe(player, data.colorVL, 1D, cc.colorActions, isMainThread)) + if (executeActions(player, data.colorVL, 1D, cc.colorActions, isMainThread)) // Remove color codes. return message.replaceAll("\302\247.", "").replaceAll("\247.", ""); } diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/GlobalChat.java b/src/fr/neatmonster/nocheatplus/checks/chat/GlobalChat.java index 89bb0726..291e6000 100644 --- a/src/fr/neatmonster/nocheatplus/checks/chat/GlobalChat.java +++ b/src/fr/neatmonster/nocheatplus/checks/chat/GlobalChat.java @@ -194,7 +194,7 @@ public class GlobalChat extends AsyncCheck implements INotifyReload{ } else{ data.globalChatVL += accumulated / 10.0; - if (executeActionsThreadSafe(player, data.globalChatVL, accumulated / 10.0, cc.globalChatActions, isMainThread)) + if (executeActions(player, data.globalChatVL, accumulated / 10.0, cc.globalChatActions, isMainThread)) cancel = true; } } diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/NoPwnage.java b/src/fr/neatmonster/nocheatplus/checks/chat/NoPwnage.java index a7ee4ca8..a49243c6 100644 --- a/src/fr/neatmonster/nocheatplus/checks/chat/NoPwnage.java +++ b/src/fr/neatmonster/nocheatplus/checks/chat/NoPwnage.java @@ -202,7 +202,7 @@ public class NoPwnage extends AsyncCheck implements ICaptcha{ data.noPwnageVL.add(now, (float) (suspicion / 10D)); // Find out if we need to kick the player or not. - cancel = executeActionsThreadSafe(player, data.noPwnageVL.getScore(cc.noPwnageVLFactor), suspicion / 10D, cc.noPwnageActions, + cancel = executeActions(player, data.noPwnageVL.getScore(cc.noPwnageVLFactor), suspicion / 10D, cc.noPwnageActions, isMainThread); } // else @@ -238,7 +238,7 @@ public class NoPwnage extends AsyncCheck implements ICaptcha{ // Does he failed too much times? if (data.noPwnageCaptchTries > cc.noPwnageCaptchaTries) { // Find out if we need to kick the player or not. - executeActionsThreadSafe(player, data.captchaVL, 1, cc.noPwnageCaptchaActions, + executeActions(player, data.captchaVL, 1, cc.noPwnageCaptchaActions, isMainThread); // (Resetting captcha tries is done on quit/kick). } @@ -334,7 +334,7 @@ public class NoPwnage extends AsyncCheck implements ICaptcha{ data.noPwnageReloginWarnings++; } else if (now - data.noPwnageReloginWarningTime < cc.noPwnageReloginWarningTimeout) // Find out if we need to ban the player or not. - cancel = executeActionsThreadSafe(player, (double) data.noPwnageVL.getScore(cc.noPwnageVLFactor), 0D, cc.noPwnageActions, true); + cancel = executeActions(player, (double) data.noPwnageVL.getScore(cc.noPwnageVLFactor), 0D, cc.noPwnageActions, true); } // Store his joining time. diff --git a/src/fr/neatmonster/nocheatplus/hooks/APIUtils.java b/src/fr/neatmonster/nocheatplus/hooks/APIUtils.java index 467557e7..90730ed7 100644 --- a/src/fr/neatmonster/nocheatplus/hooks/APIUtils.java +++ b/src/fr/neatmonster/nocheatplus/hooks/APIUtils.java @@ -74,6 +74,8 @@ public class APIUtils { /** * Return if the check type requires synchronization. + *
+ * The should be chat checks, currently. * * @param type * the check type diff --git a/src/fr/neatmonster/nocheatplus/hooks/NCPHook.java b/src/fr/neatmonster/nocheatplus/hooks/NCPHook.java index dfe41d25..a431361d 100644 --- a/src/fr/neatmonster/nocheatplus/hooks/NCPHook.java +++ b/src/fr/neatmonster/nocheatplus/hooks/NCPHook.java @@ -14,7 +14,10 @@ import fr.neatmonster.nocheatplus.checks.CheckType; * MMMMMMMMMMM MMMMMMMMMMM MMMMMMMMMMMM MMMMMMMMMMMM */ /** - * Compatibility hooks have to implement this. + * Compatibility hooks have to implement this.
+ * NOTES: + * Some checks run asynchronously, the hooks using these also have to support processing in an extra thread, check with APIUtils.needsSynchronization(CheckType). + * Hooks that can be called asynchronously must not register new hooks that might run asynchronously during processing (...). * * @author asofold */ diff --git a/src/fr/neatmonster/nocheatplus/hooks/NCPHookManager.java b/src/fr/neatmonster/nocheatplus/hooks/NCPHookManager.java index 3c46c38a..71b866e2 100644 --- a/src/fr/neatmonster/nocheatplus/hooks/NCPHookManager.java +++ b/src/fr/neatmonster/nocheatplus/hooks/NCPHookManager.java @@ -2,6 +2,7 @@ package fr.neatmonster.nocheatplus.hooks; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.LinkedList; @@ -48,6 +49,14 @@ public final class NCPHookManager { /** Mapping the check types to the hooks. */ private static final Map> hooksByChecks = new HashMap>(); + static{ + // Fill the map to be sure that thread safety can be guaranteed. + for (final CheckType type : CheckType.values()){ + if (APIUtils.needsSynchronization(type)) hooksByChecks.put(type, Collections.synchronizedList(new ArrayList())); + else hooksByChecks.put(type, new ArrayList()); + } + } + /** * Register a hook for a specific check type (all, group, or an individual check). * @@ -93,12 +102,8 @@ public final class NCPHookManager { * the hook */ private static void addToMapping(final CheckType checkType, final NCPHook hook) { - List hooks = hooksByChecks.get(checkType); - if (hooks == null) { - hooks = new ArrayList(); - hooks.add(hook); - hooksByChecks.put(checkType, hooks); - } else if (!hooks.contains(hook)) + final List hooks = hooksByChecks.get(checkType); + if (!hooks.contains(hook)) hooks.add(hook); } @@ -315,15 +320,9 @@ public final class NCPHookManager { */ private static void removeFromMappings(final NCPHook hook, final Integer hookId) { allHooks.remove(hookId); - final List rem = new LinkedList(); for (final CheckType checkId : hooksByChecks.keySet()) { - final List hooks = hooksByChecks.get(checkId); - if (hooks.remove(hook)) - if (hooks.isEmpty()) - rem.add(checkId); + hooksByChecks.get(checkId).remove(hook); } - for (final CheckType checkId : rem) - hooksByChecks.remove(checkId); } /** @@ -407,10 +406,18 @@ public final class NCPHookManager { public static final boolean shouldCancelVLProcessing(final ViolationData violationData) { // Checks for hooks registered for this event, parent groups or ALL will be inserted into the list. // Return true as soon as one hook returns true. Test hooks, if present. - final List hooksCheck = hooksByChecks.get(violationData.check.getType()); - if (hooksCheck != null) - if (applyHooks(violationData.check.getType(), violationData.player, hooksCheck)) - return true; + final CheckType type = violationData.check.getType(); + final List hooksCheck = hooksByChecks.get(type); + if (!hooksCheck.isEmpty()){ + if (APIUtils.needsSynchronization(type)){ + synchronized (hooksCheck) { + return applyHooks(type, violationData.player, hooksCheck); + } + } + else{ + return applyHooks(type, violationData.player, hooksCheck); + } + } return false; } } diff --git a/src/fr/neatmonster/nocheatplus/utilities/TickTask.java b/src/fr/neatmonster/nocheatplus/utilities/TickTask.java index f2a87c89..17c0fb89 100644 --- a/src/fr/neatmonster/nocheatplus/utilities/TickTask.java +++ b/src/fr/neatmonster/nocheatplus/utilities/TickTask.java @@ -2,6 +2,8 @@ package fr.neatmonster.nocheatplus.utilities; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Set; import org.bukkit.Bukkit; @@ -9,14 +11,16 @@ import org.bukkit.entity.Player; import fr.neatmonster.nocheatplus.NoCheatPlus; import fr.neatmonster.nocheatplus.checks.CheckType; +import fr.neatmonster.nocheatplus.checks.DelayedActionsExecution; import fr.neatmonster.nocheatplus.checks.ICheckData; /** - * Task to run every tick, to update permissions, and maybe later for extended lag measurement. + * Task to run every tick, to update permissions and execute actions, and maybe later for extended lag measurement. * @author mc_dev * */ public class TickTask implements Runnable { + protected static final class PermissionUpdateEntry{ public CheckType checkType; public String playerName; @@ -41,28 +45,51 @@ public class TickTask implements Runnable { /** Permissions to update: player name -> check type. */ private static final Set permissionUpdates = Collections.synchronizedSet(new HashSet(50)); + /** Actions to execute. */ + public static final List delayedActions = Collections.synchronizedList(new LinkedList()); + + /** Task id of the running TickTask */ + protected static int taskId = -1; + + /** + * Access method to request permisison updates. + * @param playerName + * @param checkType + */ public static void requestPermissionUpdate(final String playerName, final CheckType checkType){ permissionUpdates.add(new PermissionUpdateEntry(playerName, checkType)); } - - protected static int taskId = -1; - + public static void cancel(){ if (taskId == -1) return; Bukkit.getScheduler().cancelTask(taskId); taskId = -1; } - public static int start(final NoCheatPlus plugin){ - cancel(); - taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, new TickTask(), 1, 1); - return taskId; + private void executeActions() { + synchronized (delayedActions) { + for (final DelayedActionsExecution actions : delayedActions){ + actions.execute(); + } + } + } + + public static void requestActionsExecution(final DelayedActionsExecution actions) { + delayedActions.add(actions); } @Override public void run() { + // The isEmpty checks are faster than synchronizing fully always, the actions get delayed one tick at most. + if (!delayedActions.isEmpty()) executeActions(); if (!permissionUpdates.isEmpty()) updatePermissions(); } + + public static int start(final NoCheatPlus plugin){ + cancel(); + taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, new TickTask(), 1, 1); + return taskId; + } /** * Only call from the main thread! @@ -83,7 +110,5 @@ public class TickTask implements Runnable { permissionUpdates.clear(); } } - - }