Class TaskManager
Scans beans for Scheduled annotated methods and registers them
as Bukkit tasks. Automatically cancels all tasks when a plugin is unloaded.
Thread-confinement assumption (IN-02, gate-1 review, 16-REVIEW-residue.md):
pluginTasks/externalTasks/coreTasks are plain, non-concurrent
collections (HashMap/ArrayList). scanAndSchedule(UltiToolsPlugin, Object, Consumer)
mutates them synchronously, once per successfully-scheduled task, inside its own scan loop
(#410) - every current call site (PluginManager.onPluginRegistered,
registerScheduledMethodsCore(Object), registerScheduledMethodsExternal(String, Object)) runs during plugin loading on the main thread, so this is safe today, but nothing
enforces it. A future caller invoking any registerScheduledMethods* entry point from
an async context would race these collections with no compile-time or runtime signal - the
same main-thread-only contract GuiScheduler's
own class javadoc documents explicitly for its collections.
Config-bound tasks (#531): a Scheduled method whose period or delay is bound to
a module config key is scheduled from the key's value (seconds, times 20) and kept as a
TaskManager.BoundTask handle in boundTasks, so rescheduleBound(UltiToolsPlugin)
can find that one method's task again after /ul reload. A method with no binding takes
exactly the pre-#531 path -- same scheduler call, same bucket, same log line.
- Since:
- 6.2.0
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescription(package private) static final classPer-method record of one config-boundScheduledtask: what to invoke, where its bound values come from, its current timing in ticks, when it was armed and last ran, and its current sync Bukkit trigger. -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate final Map<UltiToolsPlugin, List<TaskManager.BoundTask>> Config-bound tasks, per module, as handles that remember their method, their binding, their current timing and their last run -- the per-method record the three buckets above do not keep.private final List<org.bukkit.scheduler.BukkitTask> Tasks owned by the framework itself rather than by a plugin module or an external plugin.private final org.bukkit.plugin.java.JavaPluginprivate final Map<UltiToolsPlugin, List<org.bukkit.scheduler.BukkitTask>> -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprivate org.bukkit.scheduler.BukkitTaskarm(TaskManager.BoundTask handle, long firstDelay) Scheduleshandle's method with its current timing, first running afterfirstDelayticks.private static PluginModuleExceptionbindingOutsideModule(Class<?> targetClass, Method method) voidcancelAll(UltiToolsPlugin plugin) Cancel all scheduled tasks for a plugin.voidCancel every framework-owned scheduled task.voidcancelAllExternal(String pluginName) Cancel all scheduled tasks for an external plugin.private static booleancheckSignatureOrWarn(Class<?> targetClass, Method method) The two signature rulesscanAndSchedule(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Object, java.util.function.Consumer<org.bukkit.scheduler.BukkitTask>)skips a method for, each logging the same WARNING it always has.private voiddispatchAsync(TaskManager.BoundTask handle) Dispatches a due body without letting worker start/completion change the trigger's phase.(package private) static StringfirstBoundMethod(Object bean) (package private) intGet the number of registered framework-owned tasks (for testing).(package private) intgetExternalTaskCount(String pluginName) Get the number of registered tasks for an external plugin (for testing).private Class<?> getTargetClass(Class<?> clazz) Get the original class, unwrapping proxies generated byProxyFactory.(package private) intgetTaskCount(UltiToolsPlugin plugin) Get the number of registered tasks for a plugin (for testing).private static voidinvokeBound(TaskManager.BoundTask handle) private static booleanisSchedulableSignature(Method method) The two signature rulesscanAndSchedule(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Object, java.util.function.Consumer<org.bukkit.scheduler.BukkitTask>)skips a method for, without its logging.(package private) static voidrefuseConfigBindings(Object bean) Refuses any config-boundScheduledmethod onbean-- for a container with no module config registry (an external plugin's).voidregisterScheduledMethods(UltiToolsPlugin plugin, Object bean) Scan a bean forScheduledmethods and register them as Bukkit tasks.voidScan a framework-owned object forScheduledmethods and register them.voidregisterScheduledMethodsExternal(String pluginName, Object bean) Scan a bean forScheduledmethods and register them for an external plugin.private static longreloadedTicks(UltiToolsPlugin plugin, TaskManager.BoundTask handle, ConfigBindings.Source source, long currentTicks, Set<String> warnedKeys, ReloadReport report) voidrescheduleBound(UltiToolsPlugin plugin) Appliesplugin's reloaded configuration to its config-boundScheduledtasks.voidrescheduleBound(UltiToolsPlugin plugin, ReloadReport report) rescheduleBound(UltiToolsPlugin), also recording inreportone partial reason per bound value kept in place of a refused one (#595), so the module's reload is not reported as complete while a task kept its old timing.private voidrescheduleOne(UltiToolsPlugin plugin, TaskManager.BoundTask handle, ReloadReport report) private voidscanAndSchedule(UltiToolsPlugin module, Object bean, Consumer<org.bukkit.scheduler.BukkitTask> recorder) Scan one bean forScheduledmethods and schedule each valid one, recording each task in its owning bucket as soon as it is created rather than batching them into a list returned at the end.private voidscheduleBound(UltiToolsPlugin module, Object bean, Class<?> targetClass, Method method, Scheduled scheduled) Schedules one config-boundScheduledmethod from its resolved binding and records its handle inboundTasksimmediately (#410).(package private) static voidvalidateConfigBindings(UltiToolsPlugin module, Object bean) Load-time check of every config-boundScheduledmethod onbean, over exactly the methodsscanAndSchedule(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Object, java.util.function.Consumer<org.bukkit.scheduler.BukkitTask>)would schedule.
-
Field Details
-
pluginTasks
-
externalTasks
-
coreTasks
Tasks owned by the framework itself rather than by a plugin module or an external plugin.A framework-owned object belongs to no
SimpleContainer, so it is reached by neitherregisterScheduledMethods(UltiToolsPlugin, Object)norregisterScheduledMethodsExternal(String, Object)-- both of which iterate a container's beans. Before 6.3.0 there was no third bucket, which is whyPlayerCacheManager.sweepExpiredEntries()carried a@Scheduledannotation that was never registered and never ran (#384).- Since:
- 6.3.0
-
boundTasks
Config-bound tasks, per module, as handles that remember their method, their binding, their current timing and their last run -- the per-method record the three buckets above do not keep. Only modules can bind, so there is no external or core counterpart.- Since:
- 6.3.0
-
hostPlugin
private final org.bukkit.plugin.java.JavaPlugin hostPlugin
-
-
Constructor Details
-
TaskManager
public TaskManager(org.bukkit.plugin.java.JavaPlugin hostPlugin)
-
-
Method Details
-
registerScheduledMethods
Scan a bean forScheduledmethods and register them as Bukkit tasks.- Parameters:
plugin- the owning plugin modulebean- the bean instance to scan
-
registerScheduledMethodsCore
Scan a framework-owned object forScheduledmethods and register them.The other two entry points key their tasks on a plugin module or an external plugin, and both are reached by iterating a
SimpleContainer's beans. An object the framework constructs directly --PlayerCacheManager, for instance -- belongs to no container and is therefore reached by neither, which is why its@Scheduledmethod silently never ran before 6.3.0 (#384). This is the third bucket, for objects the framework owns.Callers must not invoke this ad hoc. The set of framework-owned types whose scheduled methods are registered is declared by
PluginManager.FRAMEWORK_SCHEDULED_OWNER_TYPESand enforced byFrameworkScheduledWiringTest; adding an object here without adding its type there would reintroduce exactly the drift that guard exists to prevent.- Parameters:
bean- the framework-owned instance to scan- Since:
- 6.3.0
-
cancelAllCore
public void cancelAllCore()Cancel every framework-owned scheduled task.Called from
PluginManager.close(), whichUltiTools.onDisable()invokes. A repeating task that is not cancelled here survives a/reloadand a second copy is scheduled on the next enable.- Since:
- 6.3.0
-
scanAndSchedule
private void scanAndSchedule(UltiToolsPlugin module, Object bean, Consumer<org.bukkit.scheduler.BukkitTask> recorder) Scan one bean forScheduledmethods and schedule each valid one, recording each task in its owning bucket as soon as it is created rather than batching them into a list returned at the end.The single implementation behind all three registration entry points, which differ only in which bucket (
recorder) they file the resulting tasks under. Three copies of this body would be three places to keep a signature check, a scheduling rule or a log line in step.#410: before this, tasks were accumulated in a local list and handed to the owning bucket only via
created.addAll(...)in the CALLER, after this method returned. If scheduling a laterScheduledmethod threw (e.g.runTaskTimerthrowing because the host plugin became disabled mid-registration), every task already scheduled for earlier methods in this same scan was already a live Bukkit task, but the exception meant this method never returned, socreatednever reached any bucket --cancelAll/cancelAllExternal/cancelAllCorehad no record of them and could never cancel them at teardown. Callingrecorderinside the loop, immediately after each task is scheduled, means an exception from a later method can no longer un-record an earlier one: whatever the loop reached before throwing is already exactly where it needs to be for teardown to find it. This also restores the original, pre-three-bucket-split behaviour of recording each task as it was scheduled, one at a time. #531: a config-bound method is handed toscheduleBound(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Object, java.lang.Class<?>, java.lang.reflect.Method, com.ultikits.ultitools.annotations.Scheduled)instead and recorded inboundTasksas soon as it is scheduled, by the same #410 rule. Only a module has a config registry to bind against, so a bound method reached with nomodule(an external plugin's or a framework-owned bean) is refused.- Parameters:
module- the owning module, ornullfor an external plugin or the frameworkbean- the instance to scanrecorder- invoked once per successfully scheduled task, in declaration order, with that task -- files it into this bean's owning bucket immediately- Throws:
PluginModuleException- for a config-bound method with nomodule, or for a binding that cannot be resolved- Since:
- 6.3.0
-
cancelAll
Cancel all scheduled tasks for a plugin.- Parameters:
plugin- the plugin to cancel tasks for
-
scheduleBound
private void scheduleBound(UltiToolsPlugin module, Object bean, Class<?> targetClass, Method method, Scheduled scheduled) Schedules one config-boundScheduledmethod from its resolved binding and records its handle inboundTasksimmediately (#410).- Throws:
PluginModuleException- if the binding cannot be resolvedConfigurationException- if a bound value is out of range
-
arm
Scheduleshandle's method with its current timing, first running afterfirstDelayticks. Each call builds a new runnable -- aBukkitRunnablecan be scheduled only once. The trigger always runs on the main thread, where the reload step also runs. An async binding records that observed fire before handing only its method body to an async worker, one heartbeat later (#535). -
dispatchAsync
Dispatches a due body without letting worker start/completion change the trigger's phase. -
invokeBound
-
rescheduleBound
Appliesplugin's reloaded configuration to its config-boundScheduledtasks. Called byUltiToolsPlugin.reloadSelf(), throughPluginManager.applyReloadedConfigBindings, only after the configuration reload succeeded.For each bound task whose bound value changed:
- it has run before: the next run is last run + new period, or the next tick if that moment has already passed;
- it has not run yet: the first run is arm tick + new delay, or the next tick if that moment has already passed.
< 1second,null, or too large) is not applied: the running value is kept and one WARNING names the module, the key, the rejected value and the value kept.Where "the last run" comes from. Every bound task's trigger is sync, and its last fire is observed at the moment the trigger starts -- the same thread this step runs on, so the two cannot race. For an async binding, worker start and completion do not affect this stamp. Reload cancels only the trigger, leaving already-dispatched work alone (#535). No server dispatch time or boot-time scheduler clock is predicted.
Must run on the main thread, where
TaskManager's collections live; from any other thread it throws before reading them (PluginManagerchecks first and logs instead).- Parameters:
plugin- the module whose configuration was just reloaded- Since:
- 6.3.0
-
rescheduleBound
rescheduleBound(UltiToolsPlugin), also recording inreportone partial reason per bound value kept in place of a refused one (#595), so the module's reload is not reported as complete while a task kept its old timing.- Parameters:
plugin- the module whose configuration was just reloadedreport- the module's reload report- Since:
- 6.3.0
-
rescheduleOne
private void rescheduleOne(UltiToolsPlugin plugin, TaskManager.BoundTask handle, ReloadReport report) -
reloadedTicks
private static long reloadedTicks(UltiToolsPlugin plugin, TaskManager.BoundTask handle, ConfigBindings.Source source, long currentTicks, Set<String> warnedKeys, ReloadReport report) - Returns:
- the reloaded value of
sourcein ticks, orcurrentTicksif it is unbound or its new value is out of range (then warned and reported once per key)
-
validateConfigBindings
Load-time check of every config-boundScheduledmethod onbean, over exactly the methodsscanAndSchedule(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Object, java.util.function.Consumer<org.bukkit.scheduler.BukkitTask>)would schedule. Used byPluginManagerbefore any Bukkit side effect, so an invalid binding refuses the one module (#531).- Parameters:
module- the module being assembledbean- one of its container singletons- Throws:
PluginModuleException- if a binding cannot be resolvedConfigurationException- if a bound value is out of range- Since:
- 6.3.0
-
firstBoundMethod
- Parameters:
bean- a container singleton- Returns:
Class.methodof the first config-boundScheduledmethod thatscanAndSchedule(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Object, java.util.function.Consumer<org.bukkit.scheduler.BukkitTask>)would schedule, ornull- Since:
- 6.3.0
-
refuseConfigBindings
Refuses any config-boundScheduledmethod onbean-- for a container with no module config registry (an external plugin's).- Parameters:
bean- one of the container's singletons- Throws:
PluginModuleException- naming the first bound method found- Since:
- 6.3.0
-
checkSignatureOrWarn
The two signature rulesscanAndSchedule(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Object, java.util.function.Consumer<org.bukkit.scheduler.BukkitTask>)skips a method for, each logging the same WARNING it always has.- Returns:
trueifmethodtakes no parameters and returns void
-
isSchedulableSignature
The two signature rulesscanAndSchedule(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Object, java.util.function.Consumer<org.bukkit.scheduler.BukkitTask>)skips a method for, without its logging. -
bindingOutsideModule
-
getTargetClass
Get the original class, unwrapping proxies generated byProxyFactory.Delegates entirely to
ProxyFactory.unwrap(Class)- proxy identity is owned by that class, not derived here. A proxy of a proxy already carries aProxyOfmarker naming the original target, so this needs no hierarchy walk and noObject.classfallback:unwrapreturns a non-proxy argument unchanged, which is the same answer the removed fallback was approximating for the common case. -
registerScheduledMethodsExternal
Scan a bean forScheduledmethods and register them for an external plugin.- Parameters:
pluginName- the external plugin name (used as key)bean- the bean instance to scan- Since:
- 6.2.2
-
cancelAllExternal
Cancel all scheduled tasks for an external plugin.- Parameters:
pluginName- the external plugin name- Since:
- 6.2.2
-
getTaskCount
Get the number of registered tasks for a plugin (for testing). -
getCoreTaskCount
int getCoreTaskCount()Get the number of registered framework-owned tasks (for testing).- Since:
- 6.3.0
-
getExternalTaskCount
Get the number of registered tasks for an external plugin (for testing).
-