diff --git a/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/NoCheatPlus.java b/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/NoCheatPlus.java index bbce5245..ff2b361c 100644 --- a/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/NoCheatPlus.java +++ b/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/NoCheatPlus.java @@ -46,6 +46,7 @@ import fr.neatmonster.nocheatplus.components.NCPListener; import fr.neatmonster.nocheatplus.components.NameSetPermState; import fr.neatmonster.nocheatplus.components.NoCheatPlusAPI; import fr.neatmonster.nocheatplus.components.PermStateReceiver; +import fr.neatmonster.nocheatplus.components.TickListener; import fr.neatmonster.nocheatplus.config.ConfPaths; import fr.neatmonster.nocheatplus.config.ConfigFile; import fr.neatmonster.nocheatplus.config.ConfigManager; @@ -258,10 +259,15 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI { ((INeedConfig) obj).onReload(); } } + if (obj instanceof TickListener){ + TickTask.addTickListener((TickListener) obj); + } if (obj instanceof PermStateReceiver){ // No immediate update done. permStateReceivers.add((PermStateReceiver) obj); } + // Also add to DataManager, which will pick what it needs. + // TODO: This is fishy in principle, something more concise? dataMan.addComponent(obj); } @@ -305,6 +311,9 @@ public class NoCheatPlus extends JavaPlugin implements NoCheatPlusAPI { if (obj instanceof PermStateReceiver){ permStateReceivers.remove((PermStateReceiver) obj); } + if (obj instanceof TickListener){ + TickTask.removeTickListener((TickListener) obj); + } if (obj instanceof INotifyReload) { notifyReload.remove(obj); } diff --git a/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/components/TickListener.java b/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/components/TickListener.java new file mode 100644 index 00000000..601494c8 --- /dev/null +++ b/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/components/TickListener.java @@ -0,0 +1,15 @@ +package fr.neatmonster.nocheatplus.components; + +/** + * Can be registered with the TickTask. + * @author mc_dev + * + */ +public interface TickListener { + /** + * + * @param tick Current tick count. This might start over at 0 if reset in onEnable. + * @param timeLast Last time after processing loop. Allows to check how long the tick already took (roughly). No "system time ran backwards" check for this value. + */ + public void onTick(int tick, long timeLast); +} diff --git a/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/utilities/TickTask.java b/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/utilities/TickTask.java index daa639c0..9df03c51 100644 --- a/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/utilities/TickTask.java +++ b/NCPPlugin/src/main/java/fr/neatmonster/nocheatplus/utilities/TickTask.java @@ -1,5 +1,6 @@ package fr.neatmonster.nocheatplus.utilities; +import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.LinkedList; @@ -13,6 +14,7 @@ import fr.neatmonster.nocheatplus.NoCheatPlus; import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.checks.ViolationData; import fr.neatmonster.nocheatplus.checks.access.ICheckData; +import fr.neatmonster.nocheatplus.components.TickListener; import fr.neatmonster.nocheatplus.players.DataManager; /** @@ -54,6 +56,9 @@ public class TickTask implements Runnable { /** Actions to execute. */ private static final List delayedActions = new LinkedList(); + /** Tick listeners to call every tick. */ + private static final List tickListeners = new ArrayList(); + /** Last n tick durations, measured from run to run.*/ private static final long[] tickDurations = new long[lagMaxTicks]; @@ -93,7 +98,7 @@ public class TickTask implements Runnable { * Force executing actions.
* Note: Only call from the main thread! */ - public void executeActions() { + public static void executeActions() { final List copyActions = new LinkedList(); synchronized (delayedActions) { if (delayedActions.isEmpty()) return; @@ -158,6 +163,28 @@ public class TickTask implements Runnable { } } + /** + * Add a tick listener. Should be thread safe, though... why? + * @param listener + */ + public static void addTickListener(TickListener listener){ + synchronized (tickListeners) { + if (locked) return; + tickListeners.add(listener); + } + } + + /** + * Remove a tick listener. Should be thread safe, though... why? + * @param listener + * @return If previously contained. + */ + public static boolean removeTickListener(TickListener listener){ + synchronized (tickListeners) { + return tickListeners.remove(listener); + } + } + /** * Get the tasks tick count. It is increased with every server tick.
* NOTE: Can be called from other threads. @@ -318,7 +345,7 @@ public class TickTask implements Runnable { } /** - * Empty queues (call after setLocked(true) + * Empty queues (better call after setLocked(true)) and tickListeners. */ public static void purge(){ synchronized (permissionUpdates) { @@ -327,6 +354,9 @@ public class TickTask implements Runnable { synchronized (delayedActions) { delayedActions.clear(); } + synchronized (tickListeners) { + tickListeners.clear(); + } } /** @@ -345,16 +375,42 @@ public class TickTask implements Runnable { } ////////////////////////// - // Instance methods + // Instance methods (meant private). ////////////////////////// + /** + * + * Notify all listeners. + * + */ + private final void notifyListeners() { + final List copyListeners = new ArrayList(); + synchronized (tickListeners) { + // Synchronized to allow concurrent adding (!? why ?!). + // (Ignores the locked state while still running.) + copyListeners.addAll(tickListeners); + } + for (final TickListener listener : copyListeners){ + try{ + listener.onTick(tick, timeLast); + } + catch(Throwable t){ + LogUtil.logSevere("[NoCheatPlus] (TickTask) TickListener generated an exception:"); + LogUtil.logSevere(t); + } + } + } + @Override public void run() { tick ++; - // Now sync is forced, for the ability to lock. + // Actions. executeActions(); + // Permissions. updatePermissions(); + // Listeners. + notifyListeners(); // Measure time after heavy stuff. final long time = System.currentTimeMillis();