Skip to content

Working with Kelp Schedulers

PXAV edited this page Dec 31, 2020 · 1 revision

This wiki page will teach you how to use Schedulers in Kelp.

How to create a KelpScheduler

There are two different types of schedulers:

  • RepeatingScheduler: Execute a specific task repeatedly with a given delay and an optional initial delay.
  • DelayedScheduler: Perform a one-shot action with a specific delay.

Both scheduler types support executions on the server's main thread as well as an async thread.

To create a new instance of a scheduler, SchedulerFactory is needed, which you can simply inject via Guice. There you have methods such as newDelayedScheduler() to create a new scheduler instance:

public class SchedulerTest {

  private SchedulerFactory schedulerFactory;

  @Inject
  public SchedulerTest(SchedulerFactory schedulerFactory) {
    this.schedulerFactory = schedulerFactory;
  }

  public void test() {
    DelayedScheduler delayedScheduler = schedulerFactory.newDelayedScheduler();
  }

}

Configuring Schedulers with fluent builder design

By default, each scheduler is executed on the main thread. You can change that by using the async() method. An overview of all configuration methods can be found here:

For all KelpSchedulers

Method Description Example
async() The code inside the scheduler is executed on a new asynchronous thread.
sync() The code inside the scheduler is executed on the server's main thread (default value)
timeUnit(TimeUnit) Sets the TimeUnit of the scheduler's delay value (either the initial delay of DelayedScheduler or the interval of RepeatingScheduler) timeUnit(TimeUnit.MINUTES)
milliseconds() Sets the TimeUnit to MILLISECONDS
seconds() Sets the TimeUnit to SECONDS
minutes() Sets the TimeUnit to MINUTES
hours() Sets the TimeUnit to HOURS
run(KelpSchedulerRunnable) Actually starts the scheduler and provides the code to be executed in each iteration run(taskId -> System.out.println("Test"))

Specifically for DelayedScheduler

Method Description Example
withDelayOf(int) Sets the delay before the scheduler is fired and the runnable is executed. The unit for the given int is the TimeUnit described in the table above. withDelayOf(10)

Specifically for RepeatingScheduler

Method Description Example
every(int) Sets the delay between each execution. The unit for the given int is the TimeUnit described in the table above. every(1)
withInitialDelay(int) Sets the initial delay for the first execution. When the run method is called, the given amount of time has to pass before the code inside the runnable is called and the repeating delay can start. withInitialDelay(5)
waitForTaskCompletion(bool) If set to true , the repeating delay is counted after the execution of the runnable has finished. So if the repeating delay is 5 seconds and the runnable takes 1 second to finish, it would take 6 seconds between iteration. If set to false this interval will be regular and the runnable might get called if another task is still running (default) waitForTaskCompletion(true)

Examples

Waits 10 seconds, until it sends BOOOM! to the console on an async thread

schedulerFactory.newDelayedScheduler()
      .async()
      .withDelayOf(10)
      .seconds()
      .run(taskId -> {
        System.out.println("BOOOM!");
      });

Starts a scheduler counting down from 30 seconds. If the time expired, the scheduler is stopped.

public class SchedulerTest {

  private SchedulerFactory schedulerFactory;
  private KelpSchedulerRepository schedulerRepository;

  @Inject
  public SchedulerTest(SchedulerFactory schedulerFactory, KelpSchedulerRepository kelpSchedulerRepository) {
    this.schedulerFactory = schedulerFactory;
    this.schedulerRepository = kelpSchedulerRepository;
  }

  public void test() {
    AtomicInteger countDown = new AtomicInteger(30);
    schedulerFactory.newRepeatingScheduler()
      .sync()
      .every(1)
      .seconds()
      .run(taskId -> {
        System.out.println("Only " + countDown.get() + " seconds until the game starts!");

        if (countDown.get() == 0) {
          System.out.println("The game starts now, all players are teleported...");
          schedulerRepository.interruptScheduler(taskId);
          return;
        }
        
        countDown.getAndDecrement();
      });
  }

}

Thread locks and synchronization

Sending packets, teleporting players, opening sidebars, etc. is not thread-safe in bukkit and may not be executed from async threads, which is why many developers have to choose a sync scheduler, although the rest of the code is thread-safe and performance would increase if it is executed asynchronously.

This is why the ServerMainThread class of Kelp exists. It allows you to jump back easily to the main thread while having an async scheduler.

In this example, a the async thread is not blocked, but a new action is queued in the main thread as it is independent from the following actions in the async thread.

ServerMainThread.RunParallel.run(() -> {
  player.teleport(spawnLocation);
  player.sendBossBar("Welcome in the Lobby!");
});

This example blocks the async thread and lets it wait for the result processed on the server's main thread. After the result has been retrieved, the async thread can continue.

World spawnWorld = (World) ServerMainThread.WaitForCompletion.result(() -> {
  // do some complex and not thread-safe operations
  return Bukkit.getWorld(worldName);
});

Of course you can also return null to return no result, but still block the async thread until the operations have finished.

ServerMainThread.WaitForCompletion.result(() -> {
  // do some complex and not thread-safe operations
  return null;
});
Clone this wiki locally