threading required
Most scheduling operations require understanding the threading model. Read the Threading Model page first.
World as Executor
The World class implements java.util.concurrent.Executor. This means you can use standard Java executor patterns to schedule code on the world thread.
1// World implements Executor2public class World extends TickingThread implements Executor {3 public void execute(Runnable command); // Queue for next tick4 public boolean isInThread(); // Check if on world thread5 public long getTick(); // Current tick number6 public int getTps(); // Ticks per second (default 20)7}Basic Scheduling
Execute on World Thread
Use world.execute() to run code on the world thread. This is the primary way to safely modify entities from event handlers.
1// Execute code on the world thread2World world = player.getWorld();34world.execute(() -> {5 // This runs on the world thread6 // Safe to modify entities here7 player.setModel("Mouse");8});Check Current Thread
1// Check if already on world thread2if (world.isInThread()) {3 // Already safe, execute directly4 player.setModel("Mouse");5} else {6 // Need to switch threads7 world.execute(() -> {8 player.setModel("Mouse");9 });10}World Tick Information
The world runs at a configurable tick rate (default 20 TPS = 50ms per tick).
1World world = player.getWorld();23// Get current tick4long currentTick = world.getTick();56// Get TPS setting7int tps = world.getTps(); // Default: 2089// Change TPS (server-wide effect)10world.setTps(30); // 30 ticks per second| ticks | real time (at 20 tps) |
|---|---|
| 1 tick | 50ms |
| 20 ticks | 1 second |
| 1200 ticks | 1 minute |
| 24000 ticks | 20 minutes |
Task Registration
For more complex task management, use the TaskRegistry system. This integrates with CompletableFuture and ScheduledFuture.
1// TaskRegistry API2public class TaskRegistry {3 // Register a CompletableFuture task4 public TaskRegistration registerTask(5 CompletableFuture future 6 );7 8 // Register a ScheduledFuture task9 public TaskRegistration registerTask(10 ScheduledFuture future 11 );12}1314// TaskRegistration - handle to a registered task15public class TaskRegistration {16 public Future> getTask(); // Get the underlying future17}Async Patterns
CompletableFuture Integration
Use Java's CompletableFuture for async operations, then switch back to the world thread for entity modifications.
1// Async operation with CompletableFuture2CompletableFuture.supplyAsync(() -> {3 // Do heavy work off the world thread4 return loadDataFromDatabase(playerId);5}).thenAccept(data -> {6 // Back on common pool - still need world.execute()!7 world.execute(() -> {8 // Now safe to modify entities9 applyDataToPlayer(player, data);10 });11});Chunk Loading (Async)
Some operations like chunk loading are inherently async:
1// Async chunk loading2long chunkKey = /* packed chunk coordinates */;34CompletableFuture future = world.getChunkAsync(chunkKey); 56future.thenAccept(chunk -> {7 if (chunk != null) {8 // Chunk is loaded9 world.execute(() -> {10 // Process chunk on world thread11 });12 }13});Common Patterns
Delayed Action
1// Simple delayed action using tick counting2public class DelayedAction {3 private final World world;4 private final long targetTick;5 private final Runnable action;6 7 public DelayedAction(World world, int delayTicks, Runnable action) {8 this.world = world;9 this.targetTick = world.getTick() + delayTicks;10 this.action = action;11 }12 13 // Call this each tick from an event listener14 public boolean tryExecute() {15 if (world.getTick() >= targetTick) {16 world.execute(action);17 return true; // Action executed18 }19 return false; // Still waiting20 }21}Repeating Task
1// Using ScheduledExecutorService for repeating tasks2import java.util.concurrent.*;34public class RepeatingTask {5 private final ScheduledExecutorService scheduler = 6 Executors.newSingleThreadScheduledExecutor();7 8 public void startRepeatingTask(World world, Player player) {9 scheduler.scheduleAtFixedRate(() -> {10 // This runs on scheduler thread11 world.execute(() -> {12 // Safe entity modification13 healPlayer(player, 1);14 });15 }, 0, 1, TimeUnit.SECONDS); // Every 1 second16 }17 18 public void stop() {19 scheduler.shutdown();20 }21}Best Practices
| do | don't |
|---|---|
| Use world.execute() for entity changes | Modify entities from scheduler threads directly |
| Check isInThread() before execute() | Assume you're on the world thread |
| Clean up schedulers in plugin stop() | Leave schedulers running after plugin stops |
| Use CompletableFuture for async work | Block the world thread with long operations |
| Keep world.execute() blocks short | Put heavy computation inside world.execute() |
scheduler cleanup
Always shut down your
ScheduledExecutorService instances in your plugin's stop() method to prevent resource leaks.Quick Reference
SchedulingExample.javajava
1public class SchedulingExample extends JavaPlugin {2 private ScheduledExecutorService scheduler;3 4 @Override5 protected void start() {6 scheduler = Executors.newSingleThreadScheduledExecutor();7 8 getEventRegistry().register(9 PlayerConnectEvent.class,10 this::onPlayerConnect11 );12 }13 14 @Override15 protected void stop() {16 // Always clean up!17 if (scheduler != null) {18 scheduler.shutdown();19 }20 }21 22 private void onPlayerConnect(PlayerConnectEvent event) {23 Player player = event.getPlayer();24 World world = player.getWorld();25 26 // Immediate action on world thread27 world.execute(() -> {28 player.sendMessage("Welcome!");29 });30 31 // Delayed action (5 seconds later)32 scheduler.schedule(() -> {33 world.execute(() -> {34 player.sendMessage("Enjoy your stay!");35 });36 }, 5, TimeUnit.SECONDS);37 }38}