-
-
Notifications
You must be signed in to change notification settings - Fork 254
Add Scheduler #143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Scheduler #143
Changes from all commits
e5ebed0
c6edc76
4788e9f
8912523
a3e325a
cd47320
2402cdf
3f4a10c
a512fd8
1dfbc9d
181ace1
a7b3f42
170d393
5e70ed3
cd14a37
daa5513
6d2af28
8ab3f4d
1e4872d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,197 @@ | ||||||||||||||||||||||
| --- | ||||||||||||||||||||||
| slug: /dev/scheduler | ||||||||||||||||||||||
| --- | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Task Scheduler | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| The `BukkitScheduler` can be used to schedule your code to be run later or run it repeatedly. | ||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will change |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## What is a tick? | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Every game runs something called a game loop which essentially executes all the logic of the game over and over, | ||||||||||||||||||||||
| a single execution of that loop is called a 'tick'. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| In Minecraft's case the amount of ticks per second is 20, or one tick every 50 milliseconds, | ||||||||||||||||||||||
| meaning that the game loop is executed 20 times per second. A tick taking more than 50ms to execute is the moment | ||||||||||||||||||||||
| when your server starts to fall behind on its work and lag. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Converting between human units and Minecraft ticks | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Every method of the scheduler that takes a delay or period uses ticks as a unit of time. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Converting from human units to ticks and back is as simple as: | ||||||||||||||||||||||
| `ticks = seconds * 20` | ||||||||||||||||||||||
|
Nacioszeczek marked this conversation as resolved.
|
||||||||||||||||||||||
| `seconds = ticks / 20` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| You can make your code more readable by using the | ||||||||||||||||||||||
| [`TimeUnit`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/TimeUnit.html) | ||||||||||||||||||||||
| enum, e.g. to convert 5 minutes to ticks and back: | ||||||||||||||||||||||
| `TimeUnit.MINUTES.toSeconds(5) * 20` | ||||||||||||||||||||||
| `TimeUnit.SECONDS.toMinutes(ticks / 20)` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Obtaining the scheduler | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| To obtain a scheduler you can use the instance method on the `Server` class, e.g. in your `onEnable` method: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```java | ||||||||||||||||||||||
| @Override | ||||||||||||||||||||||
| public void onEnable() { | ||||||||||||||||||||||
| BukkitScheduler scheduler = this.getServer().getScheduler(); | ||||||||||||||||||||||
|
Nacioszeczek marked this conversation as resolved.
|
||||||||||||||||||||||
| } | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Scheduling tasks | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Scheduling a task requires you to pass: | ||||||||||||||||||||||
| - your plugin's instance, | ||||||||||||||||||||||
| - the code to run, either with a `Runnable` or `Consumer<BukkitTask>`, | ||||||||||||||||||||||
| - the delay in ticks before the task should run, | ||||||||||||||||||||||
| - if you're scheduling a repeating task - the period in ticks between each execution of the task. | ||||||||||||||||||||||
|
Comment on lines
+46
to
+49
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Think of it as a long sentence, hence the commas and the period at the end, |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Difference between synchronous and asynchronous tasks | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| #### Synchronous tasks (on the main thread) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Synchronous tasks are tasks that are executed on the main server thread. This is the same | ||||||||||||||||||||||
| thread that handles all game logic. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| All tasks scheduled on the main thread will affect the server's performance. If your task | ||||||||||||||||||||||
| is making web requests, accessing files, databases or otherwise time-consuming operations you should consider using | ||||||||||||||||||||||
| an asynchronous task. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| #### Asynchronous tasks (off the main thread) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Asynchronous tasks are tasks that are executed on separate threads, therefore will not affect | ||||||||||||||||||||||
| your server's performance. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| :::warning | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| **You cannot safely access the Bukkit API from within asynchronous tasks**. If a method is not explicitly marked | ||||||||||||||||||||||
| as thread-safe or isn't obviously designed to be used asynchronously **you will eventually encounter errors**. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ::: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| :::info | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| While the tasks are executed on separate threads, they are still started from the main thread | ||||||||||||||||||||||
| and will be affected if the server is lagging, an example would be 20 ticks not being exactly 1 second. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| If you need a scheduler that runs independently of the server consider using your own | ||||||||||||||||||||||
| [`ScheduledExecutorService`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/ScheduledExecutorService.html). | ||||||||||||||||||||||
| You can follow [this guide](https://www.baeldung.com/java-executor-service-tutorial#ScheduledExecutorService) to learn how to use it. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ::: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Difference between `Runnable` and `Consumer<BukkitTask>` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| #### Using `Runnable` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| The `Runnable` interface is used for the simplest tasks that don't require a `BukkitTask` instance. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| You can either implement it in a separate class, e.g.: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```java | ||||||||||||||||||||||
| public class MyRunnableTask implements Runnable { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private final MyPlugin plugin; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| public MyRunnableTask(MyPlugin plugin) { | ||||||||||||||||||||||
| this.plugin = plugin; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @Override | ||||||||||||||||||||||
| public void run() { | ||||||||||||||||||||||
| this.plugin.getServer().broadcast(Component.text("Hello, World!")); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| } | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
| ```java | ||||||||||||||||||||||
| scheduler.runTaskLater(plugin, new MyRunnableTask(plugin), 20); | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Or use a lambda expression which is great for simple and short tasks: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```java | ||||||||||||||||||||||
| scheduler.runTaskLater( | ||||||||||||||||||||||
| plugin, | ||||||||||||||||||||||
| // Lambda: | ||||||||||||||||||||||
| () -> { | ||||||||||||||||||||||
| this.plugin.getServer().broadcast(Component.text("Hello, World!")); | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| // End of the lambda | ||||||||||||||||||||||
| 20); | ||||||||||||||||||||||
|
Comment on lines
+117
to
+123
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Keeps it styled the same as the next example and looks cleaner
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will change |
||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| #### Using `Consumer<BukkitTask>` {#using-consumerbukkittask} | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| The `Consumer` interface is used for tasks that require a `BukkitTask` instance (usually in repeated tasks), | ||||||||||||||||||||||
| e.g. when you want to cancel the task from inside it. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| You can either implement it in a separate class similarly to the `Runnable`, e.g.: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```java | ||||||||||||||||||||||
| public class MyConsumerTask implements Consumer<BukkitTask> { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private final LivingEntity entity; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| public MyConsumerTask(LivingEntity entity) { | ||||||||||||||||||||||
| this.entity = entity; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @Override | ||||||||||||||||||||||
| public void accept(BukkitTask task) { | ||||||||||||||||||||||
| if(this.entity.isDead()) { | ||||||||||||||||||||||
| task.cancel(); // The entity died, there's no point | ||||||||||||||||||||||
| return; // in running the code anymore. | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| this.entity.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 20, 1)); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| } | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
| ```java | ||||||||||||||||||||||
| scheduler.runTaskTimer(plugin, new MyConsumerTask(someEntity), 0, 20); | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Or use a lambda expression which again is much cleaner for short and simple tasks: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```java | ||||||||||||||||||||||
| scheduler.runTaskTimer(plugin, /* Lambda: */ task -> { | ||||||||||||||||||||||
| if(this.entity.isDead()) { | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing a space between |
||||||||||||||||||||||
| task.cancel(); | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| this.entity.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 20, 1)); | ||||||||||||||||||||||
| } /* End of the lambda */, 0, 20); | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Examples of tasks on the main thread | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### A task to run later once | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| This task will run a single time after the delay specified in ticks, in this case 1 second. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```java | ||||||||||||||||||||||
| scheduler.runTaskLater(plugin, () -> { | ||||||||||||||||||||||
| server.broadcast(Component.text("Hello, World!")); | ||||||||||||||||||||||
| }, 20); | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### A task to run later repeatedly | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| This task will run repeatedly, first time after the delay specified in ticks - in this case 1 second - | ||||||||||||||||||||||
| and then every period specified in ticks - in this case 5 seconds. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```java | ||||||||||||||||||||||
| scheduler.runTaskTimer(plugin, () -> { | ||||||||||||||||||||||
| server.broadcast(Component.text("Hello, World!")); | ||||||||||||||||||||||
| }, 20, 5 * 20); | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### A repeating task to be canceled later | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Cancelling a repeating task requires you to have an instance of a `BukkitTask`. | ||||||||||||||||||||||
| After obtaining it, simply use the `cancel()` method. | ||||||||||||||||||||||
| The example on how to use a [`Consumer<BukkitTask>`](#using-consumerbukkittask) already shows exactly how to do it. | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the page called task scheduler if you immediately refer to it with the
BukkitSchedulername. Maybe just name the page "The Bukkit Scheduler" or something along those lines?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer something like
Scheduling tasksover including Bukkit in the name