Scheduler¶
Every component can inject a Scheduler. It is an ambient root
like the plugin itself and needs no declaration. When the graph unloads, weftkit cancels every
task scheduled through it.
Component-owned tasks¶
Inject TaskScope into a singleton for tasks tied to that component. Each consumer gets its
own scope, cancelled before its unload, including failed construction or startup. Other
components' tasks keep running. The bundled bukkit:scheduler module supplies it.
@Wired
@Singleton
final class HomesFile implements Loader {
private final TaskScope tasks;
HomesFile(JavaPlugin plugin, TaskScope tasks) {
this.tasks = tasks;
...
}
@Override
public boolean load() {
...
tasks.global().runRepeating(this::save, 20L * 60 * 5, 20L * 60 * 5);
return true;
}
@Override
public void unload() {
save();
}
}
Import TaskScope from org.weftkit.wiring.bukkit.scheduler. The player-homes example
uses this pattern.
TaskScope has the same scheduling API as Scheduler. Closing it rejects new work, including
through retained region and async handles, and leaves the parent scheduler open. Already-running
callbacks may finish; closing does not interrupt or wait for them.
Use scheduler.scope() for manual ownership and close it yourself, for example with
try-with-resources. Only singletons can inject TaskScope; prototypes use manual ownership.
The API¶
The interface follows Folia's threaded regions. Every task names the Region it runs in or runs
async(). On Bukkit and Paper every region is the main thread.
| Region | Bukkit and Paper | Folia |
|---|---|---|
global() |
Main thread | Global region scheduler |
at(Location) |
Main thread | The region owning the Location |
on(Entity) |
Main thread | The Entity's own scheduler |
async() |
Bukkit's async pool | Folia's async scheduler |
A Region offers:
run(task): runs on the next tick.runLater(task, delayTicks): runs after the delay. A delay of0is the next tick.runRepeating(task, delayTicks, periodTicks): runs every period after the delay.isCurrentThread(): whether the current thread ticks this region.
async() offers the same three scheduling methods with Durations instead of ticks. Bukkit
rounds a Duration up to whole ticks. Folia keeps millisecond precision.
scheduler.at(location).run(() -> world.spawnParticle(...));
scheduler.on(player).runLater(() -> player.sendMessage("..."), 20);
scheduler.async().run(this::saveToDisk);
Every scheduling method returns a Task handle with cancel() and isCancelled(). On Folia, a
task whose entity was removed before it ran reports cancelled.
Check threads with isCurrentThread(), not Bukkit.isPrimaryThread(). Folia has no single main
thread.
Lifecycle¶
On disable, weftkit tears down in this order:
- The modules deactivate.
- The singletons unload in reverse creation order, each closing its owned resources before its hook.
- The bundled
bukkit:schedulermodule, loaded first and so unloaded last, closes theSchedulerand cancels every task scheduled through it.
A module that starts a repeating task in activate cancels it in deactivate.
The announcer example does this.
When startup aborts because a load hook failed, the same unload runs for the components that
did load, whichever Scheduler the graph runs with.
Tasks scheduled through the shared Scheduler remain queued until the scheduler module closes;
component-owned tasks have already been cancelled before their owner's unload hook. Disable
the module with @Registry(disable = BukkitModules.SCHEDULER) to cancel by hand.
cancelAll belongs to the adapter and only touches tasks scheduled through the Scheduler.
Cancel your own tasks through their Task handles. A task scheduled directly with the platform
scheduler stays the plugin's to cancel.
Teardown calls Scheduler.close(). Built-in schedulers permanently reject new work with
IllegalStateException, including calls through retained Region and Async handles; use a
fresh scheduler for a new enable. Pending tasks are cancelled, late platform handles are
cancelled on arrival, and callbacks already running may finish. cancelAll() remains reusable.
Custom schedulers should override close() to close admission; its compatibility default only
calls cancelAll().
Folia¶
Any weftkit plugin runs on Folia after two changes. This applies to plugin.yml and
paper-plugin.yml plugins alike.
-
Add
weftkit-foliato the dependencies that go into the jar. -
Declare
folia-supported: trueinplugin.ymlorpaper-plugin.yml.
With weftkit-folia on the compile classpath, the Bukkit processor weaves the folia:scheduler
module into the registry. The module's SchedulerDetection component
provides the Scheduler. It picks Folia's regions on
Folia and the Bukkit scheduler on every other server. On servers other than Folia, no Folia class
is loaded.
weftkit-folia requires Folia 1.20.1 or newer.
Without weftkit-folia, a plugin that enables on Folia fails with this message:
Folia detected. Add weftkit-folia to the plugin's dependencies, or pass a Scheduler to BukkitWeft.enable
Three ways exist to override the detected scheduler:
- A
scheduler()override in the plugin main replaces it. - A scheduler passed to
BukkitWeft.enablereplaces it. @Registry(disable = FoliaModules.SCHEDULER)drops the module.
BukkitWeft.scheduler(loader) returns the scheduler a loader runs with.
The Scheduler leaves out two parts of Folia's API. Use Paper's API directly for them:
- A custom retired callback for entity tasks. weftkit's callback marks the
Taskcancelled. - Region tasks addressed by world and chunk coordinates instead of a
Location.
The bundled metrics module also schedules through the Scheduler.
Related¶
- Lifecycle: where cancellation sits in startup and shutdown
- Components: ambient roots
- player-homes: an autosave task left to weftkit to cancel
- announcer: a
plugin.ymlplugin that also runs on Folia - warps: a
paper-plugin.ymlplugin that does the same