Skip to content

These docs describe Bloom 0.1.0, a pre-release. APIs may change before 1.0, and Bloom is not on Maven Central yet (see Installation).

Scheduling

Run code later, repeatedly or off the main thread, with tasks that are cleaned up automatically.

bloom.scheduler() returns a BloomScheduler. Every task belongs to your plugin and is cancelled by bloom.shutdown(). If a task throws, the exception is logged with your plugin's name and other tasks keep running.

Main-thread tasks

Use sync* methods for anything that touches players, worlds, entities or inventories.

JavaView source
BloomScheduler scheduler = bloom.scheduler();
scheduler.sync(() -> player.sendMessage("Next tick"));
scheduler.syncLater(() -> player.sendMessage("Three seconds later"), Duration.ofSeconds(3));
ScheduledTask task = scheduler.syncRepeating(self -> player.sendMessage("Every second"), 0, 20);
task.cancel();

Delays and periods are in ticks (20 per second) or Durations, which are rounded up to whole ticks. A delay of zero means the next tick, as in Bukkit.

Repeating tasks that stop themselves

Repeating tasks receive their own handle, so they can cancel themselves without a field:

JavaView source
final int[] seconds = {5};
bloom.scheduler()
        .syncRepeating(
                task -> {
                    if (!player.isOnline() || seconds[0] == 0) {
                        task.cancel(); // a repeating task receives its own handle
                        return;
                    }
                    bloom.messages().actionBar(player, "&eStarting in " + seconds[0]--);
                },
                Duration.ZERO,
                Duration.ofSeconds(1));

Background work

async* methods run on background threads. Use them for files, web requests and heavy computation, then come back to the main thread before touching the game:

JavaView source
bloom.scheduler()
        .supplyAsync(() -> loadProfileFromWeb(player.getUniqueId())) // background thread: no Bukkit calls
        .thenAcceptAsync(
                profile -> player.sendMessage("Loaded " + profile), // back on the main thread
                bloom.scheduler().mainThreadExecutor());

Thread checks

JavaView source
bloom.scheduler().ensurePrimaryThread("Teleporting players");
// IllegalStateException off the main thread: "Teleporting players must be called from the main server
// thread, but was called from thread 'pool-3-thread-1'. Switch threads with scheduler().sync(...) ..."

Bloom's own main-thread APIs (menus, boss bars) perform this check for you.

Reference

MethodRuns onNotes
sync(Runnable)mainnext tick
syncLater(Runnable, ticks or Duration)main
syncRepeating(Consumer<ScheduledTask>, delay, period)mainperiod at least one tick
async(Runnable) / asyncLater / asyncRepeatingbackground
supplySync(Supplier) / supplyAsync(Supplier)main / backgroundreturns CompletableFuture; cancelled futures fail with CancellationException on shutdown
mainThreadExecutor() / asyncExecutor()main / backgroundfor CompletableFuture chains
activeTaskCount()tasks not yet finished or cancelled

Folia is not supported yet, because its region threads do not fit a single main thread.