FoliaScheduler is a fully-featured task scheduler for plugins seeking Spigot/Paper/Folia support. With clean syntax, you can now schedule tasks on entities, regions, asynchronously, and more!
- Java 1.8+ (but you will need a JDK of 17+ to shade the Folia compatibility classes!)
- Folia 1.20+
- Spigot/Paper 1.12.2+ (and likely any older version)
- Task chaining w/ callback values (
CompletableFuture<T>
support) - Easy task cancellation through both
Consumer<TaskImplementation<T>>
and return values. ServerVersions
andMinecraftVersions
utility classes for checking server type and version.ReflectionUtil
with automatic remapping for Paper remapping compatibility.
We use Folia's scheduler (included in paper server jars) if it is available. If not, we fallback to
Spigot's scheduler, BukkitRunnable
. This adds support for practically any server.
<dependency>
<groupId>com.cjcrafter</groupId>
<artifactId>foliascheduler</artifactId>
<version>0.6.3</version>
</dependency>
[Full Maven + Shade example]
<dependencies>
<dependency>
<groupId>com.cjcrafter</groupId>
<artifactId>foliascheduler</artifactId>
<version>0.6.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version> <!-- always check for latest -->
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>com.cjcrafter.foliascheduler</pattern>
<shadedPattern>com.example.foliascheduler</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
repositories {
mavenCentral()
}
dependencies {
implementation("com.cjcrafter:foliascheduler:0.6.3")
}
[Full Gradle + Shade example]
plugins {
java // or kotlin("jvm") version "..."
//id("com.github.johnrengelman.shadow") version "8.1.1" // for below Java 21... always check for latest
id("io.github.gooler.shadow") version "8.1.7" // for Java 21+... always check for latest
id("net.minecrell.plugin-yml.bukkit") version "0.6.0" // always check for latest
}
repositories {
mavenCentral()
}
dependencies {
// TODO add your version of Spigot/Paper here
implementation("com.cjcrafter:foliascheduler:0.6.3")
}
// See https://github.com/Minecrell/plugin-yml
bukkit {
main = "com.example.MyPlugin"
foliaSupported = true
}
tasks.shadowJar {
archiveFileName.set("MyPlugin-${project.version}.jar")
relocate("com.cjcrafter.foliascheduler", "com.example.foliascheduler")
}
Your plugin is SO CLOSE to working on Folia, except you need to change usage of
BukkitRunnable
and
Bukkit#getScheduler
,
and add folia-supported: true
to your plugin.yml.
Not sure where to start? Take a look at these projects that used this library to add Folia:
- Before modifying any block/entity, you can check if your thread is correct by using:
ServerImplementation#isOwnedByCurrentRegion(Entity)
ServerImplementation#isOwnedByCurrentRegion(Block)
- Typically, you should not use
ServerImplementation#async()
unless you are doing I/O operations or heavy calculations.- When you use async, you should probably use a sync callback (See examples below)
- It is safe to send packets to players on any thread.
We provide 2 utility classes:
ServerVersions
- CheckisFolia()
andisPaper()
MinecraftVersions
- Get currentmajor.minor.patch
version
if (!MinecraftVersions.UPDATE_AQUATIC.isAtLeast()) {
getLogger().warning("Uh oh! You're not on 1.13+! This plugin may not work correctly!");
// disable plugin
}
Plugin plugin = null;
Entity entity = null;
// You should re-use this instance
ServerImplementation scheduler = new FoliaCompatibility(plugin).getServerImplementation();
scheduler.entity(entity).run(() -> {
entity.setVelocity(new Vector(0, 1, 0));
});
Plugin plugin = null;
// You should re-use this instance
ServerImplementation scheduler = new FoliaCompatibility(plugin).getServerImplementation();
scheduler.async().runDelayed(task -> {
plugin.getLogger().info("This is the scheduled task! I'm async! " + task);
}, 5 * 20L);
Plugin plugin = null;
File file = null;
// You should re-use this instance
ServerImplementation scheduler = new FoliaCompatibility(plugin).getServerImplementation();
TaskImplementation<String> scheduledTask = scheduler.async().runNow(task -> {
String contents = "Read the file";
return contents;
});
// Use "thenCompose" to chain many tasks together
CompletableFuture<TaskImplementation<Void>> composed = scheduledTask.asFuture().thenCompose(task -> {
return scheduler.global().run(nextTask -> {
System.out.println("We're back on the global thread: " + scheduledTask.getCallback());
}).asFuture();
});
composed.thenAccept(task -> {
System.out.println("We finished everything now!");
});
Paper and Spigot often "disagree" on what to name a class, field, or method. This is a major problem if you plan on using reflection to access these classes. Like this example:
Class.forName("net.minecraft.world.entity.monster.EntityCreeper"); // Works on Spigot servers
Class.forName("net.minecraft.world.entity.monster.Creeper"); // Works on Paper servers
To solve this, we provide a utility class called ReflectionUtil
that will automatically remap classes, fields, and methods for you. The example
above can be solved by using the code below:
// Always provide a Spigot-mapped class, and we'll remap as needed
Class<?> entityCreeper = ReflectionUtil.getMinecraftClass("net.minecraft.world.entity.monster.EntityCreeper");